From d91b97bc023b669e4ba1b5ccfca341f597d7fb9e Mon Sep 17 00:00:00 2001 From: morethanwords Date: Thu, 15 Oct 2020 01:46:29 +0300 Subject: [PATCH] Media viewer: Avatars are now viewable, with pagination Fix border-radius transition --- src/components/appMediaViewer.ts | 128 ++++++++++++++-------- src/components/avatar.ts | 58 ++++++++-- src/lib/appManagers/appMessagesManager.ts | 18 ++- src/lib/appManagers/appPhotosManager.ts | 30 ++--- 4 files changed, 165 insertions(+), 69 deletions(-) diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index a64b47f1..2da43871 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -7,7 +7,6 @@ import appImManager from "../lib/appManagers/appImManager"; import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appPeersManager from "../lib/appManagers/appPeersManager"; import appPhotosManager from "../lib/appManagers/appPhotosManager"; -import appProfileManager from "../lib/appManagers/appProfileManager"; import { logger } from "../lib/logger"; import VideoPlayer from "../lib/mediaPlayer"; import { RichTextProcessor } from "../lib/richtextprocessor"; @@ -49,7 +48,7 @@ class AppMediaViewerBase; - protected peerID = 0; + protected isFirstOpen = true; protected loadMediaPromiseUp: Promise = null; protected loadMediaPromiseDown: Promise = null; protected loadedAllMediaUp = false; @@ -221,7 +220,6 @@ class AppMediaViewerBase onAnimationEnd); @@ -786,15 +784,14 @@ class AppMediaViewerBase { public currentMessageID = 0; + public peerID: number; - constructor() { + constructor(private inputFilter: 'inputMessagesFilterPhotoVideo' | 'inputMessagesFilterChatPhotos' = 'inputMessagesFilterPhotoVideo') { super(['delete', 'forward']); const stub = document.createElement('div'); @@ -1110,16 +1108,17 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet this.author.container.addEventListener('click', this.onAuthorClick); } - public close(e?: MouseEvent) { + /* public close(e?: MouseEvent) { const good = !this.setMoverAnimationPromise; const promise = super.close(e); if(good) { // clear this.currentMessageID = 0; + this.peerID = 0; } return promise; - } + } */ onPrevClick = (target: AppMediaViewerTargetType) => { this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID}); @@ -1138,7 +1137,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet }; onAuthorClick = (e: MouseEvent) => { - if(this.currentMessageID) { + if(this.currentMessageID && this.currentMessageID != Number.MAX_SAFE_INTEGER) { const mid = this.currentMessageID; this.close(e).then(() => { const message = appMessagesManager.getMessage(mid); @@ -1168,8 +1167,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet protected loadMoreMedia = (older = true) => { //if(!older && this.reverse) return; - if(older && this.loadedAllMediaDown) return; - else if(!older && this.loadedAllMediaUp) return; + if(older && this.loadedAllMediaDown) return Promise.resolve(); + else if(!older && this.loadedAllMediaUp) return Promise.resolve(); if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown; else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp; @@ -1191,7 +1190,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet const peerID = this.peerID; const promise = appMessagesManager.getSearch(peerID, '', - {_: 'inputMessagesFilterPhotoVideo'}, maxID, loadCount/* older ? loadCount : 0 */, 0, backLimit).then(value => { + {_: this.inputFilter}, maxID, loadCount/* older ? loadCount : 0 */, 0, backLimit).then(value => { if(this.peerID != peerID) { this.log.warn('peer changed'); return; @@ -1212,10 +1211,10 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet const method = older ? value.history.forEach : value.history.forEachReverse; method.call(value.history, mid => { const message = appMessagesManager.getMessage(mid); - const media = message.media; + const media = this.getMediaFromMessage(message); - if(!media || !(media.photo || media.document || (media.webpage && media.webpage.document))) return; - if(media._ == 'document' && media.type != 'video') return; + if(!media) return; + //if(media._ == 'document' && media.type != 'video') return; const t = {element: null as HTMLElement, mid: mid}; if(older) { @@ -1227,8 +1226,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet } }); - this.buttons.prev.style.display = this.prevTargets.length ? '' : 'none'; - this.buttons.next.style.display = this.nextTargets.length ? '' : 'none'; + this.buttons.prev.classList.toggle('hide', !this.prevTargets.length); + this.buttons.next.classList.toggle('hide', !this.nextTargets.length); }, () => {}).then(() => { if(older) this.loadMediaPromiseDown = null; else this.loadMediaPromiseUp = null; @@ -1240,6 +1239,12 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet return promise; }; + private getMediaFromMessage(message: any) { + return message.action ? message.action.photo : message.media.photo + || message.media.document + || (message.media.webpage && (message.media.webpage.document || message.media.webpage.photo)); + } + private setCaption(message: any) { const caption = message.message; if(caption) { @@ -1255,20 +1260,21 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) { if(this.setMoverPromise) return this.setMoverPromise; + const mid = message.mid; const fromID = message.fromID; - const media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo; + const media = this.getMediaFromMessage(message); - const isFirstOpen = !this.peerID; let fromRight = 0; - if(!isFirstOpen) { + if(!this.isFirstOpen) { //if(this.lastTarget === prevTarget) { - if(this.reverse) fromRight = this.currentMessageID < message.mid ? 1 : -1; - else fromRight = this.currentMessageID > message.mid ? 1 : -1; + if(this.reverse) fromRight = this.currentMessageID < mid ? 1 : -1; + else fromRight = this.currentMessageID > mid ? 1 : -1; } else { this.reverse = reverse; + this.peerID = $rootScope.selectedPeerID; } - this.currentMessageID = message.mid; + this.currentMessageID = mid; const promise = super._openMedia(media, fromID, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore); this.setCaption(message); @@ -1276,10 +1282,16 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet } } -export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', {}> { - constructor() { +type AppMediaViewerAvatarTargetType = {element: HTMLElement, photoID: string}; +export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMediaViewerAvatarTargetType> { + public currentPhotoID: string; + public peerID: number; + + constructor(peerID: number) { super(['delete']); + this.peerID = peerID; + this.setBtnMenuToggle([{ icon: 'download', text: 'Download', @@ -1295,33 +1307,63 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', {}> { this.setListeners(); } + onPrevClick = (target: AppMediaViewerAvatarTargetType) => { + this.nextTargets.unshift({element: this.lastTarget, photoID: this.currentPhotoID}); + this.openMedia(target.photoID, target.element, -1); + }; + + onNextClick = (target: AppMediaViewerAvatarTargetType) => { + this.prevTargets.push({element: this.lastTarget, photoID: this.currentPhotoID}); + this.openMedia(target.photoID, target.element, 1); + }; + onDownloadClick = (e: MouseEvent) => { }; protected loadMoreMedia = (older = true) => { - return Promise.resolve(); + if(this.peerID < 0) return Promise.resolve(); // ! это значит, что открыло аватар чата, но следующих фотографий нет. + if(this.loadedAllMediaDown) return Promise.resolve(); + if(this.loadMediaPromiseDown) return this.loadMediaPromiseDown; + + const peerID = this.peerID; + const loadCount = 50; + + const maxID = this.nextTargets.length ? this.nextTargets[this.nextTargets.length - 1].photoID : this.currentPhotoID; + + const promise = appPhotosManager.getUserPhotos(peerID, maxID, loadCount).then(value => { + if(this.peerID != peerID) { + this.log.warn('peer changed'); + return; + } + + this.log('loaded more media by maxID:', /* maxID, */value, older, this.reverse); + + if(value.photos.length < loadCount) { + this.loadedAllMediaDown = true; + } + + value.photos.forEach(photoID => { + if(this.currentPhotoID == photoID) return; + this.nextTargets.push({element: null as HTMLElement, photoID}); + }); + + this.buttons.prev.classList.toggle('hide', !this.prevTargets.length); + this.buttons.next.classList.toggle('hide', !this.nextTargets.length); + }, () => {}).then(() => { + this.loadMediaPromiseDown = null; + }); + + return this.loadMediaPromiseDown = promise; }; - public async openMedia(peerID: number, target?: HTMLElement, reverse = false, - prevTargets: AppMediaViewerAvatar['prevTargets'] = [], nextTargets: AppMediaViewerAvatar['prevTargets'] = [], needLoadMore = true) { + public async openMedia(photoID: string, target?: HTMLElement, fromRight = 0) { if(this.setMoverPromise) return this.setMoverPromise; - return appProfileManager.getFullPhoto(peerID).then(photo => { - const fromID = peerID; + const photo = appPhotosManager.getPhoto(photoID); - const isFirstOpen = !this.peerID; - let fromRight = 0; - if(!isFirstOpen) { - //if(this.lastTarget === prevTarget) { - /* if(this.reverse) fromRight = this.currentMessageID < message.mid ? 1 : -1; - else fromRight = this.currentMessageID > message.mid ? 1 : -1; */ - fromRight = 1; - } else { - this.reverse = reverse; - } + this.currentPhotoID = photo.id; - const promise = super._openMedia(photo, fromID, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore); - }); + return super._openMedia(photo, this.peerID, fromRight, target, false); } } diff --git a/src/components/avatar.ts b/src/components/avatar.ts index f6786055..332b7ea3 100644 --- a/src/components/avatar.ts +++ b/src/components/avatar.ts @@ -1,7 +1,8 @@ +import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appProfileManager from "../lib/appManagers/appProfileManager"; import $rootScope from "../lib/rootScope"; import { cancelEvent } from "../lib/utils"; -import { AppMediaViewerAvatar } from "./appMediaViewer"; +import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer"; $rootScope.$on('avatar_update', (e) => { let peerID = e.detail; @@ -30,17 +31,60 @@ export default class AvatarElement extends HTMLElement { this.isDialog = !!this.getAttribute('dialog'); if(this.getAttribute('clickable') === '') { this.setAttribute('clickable', 'set'); - this.addEventListener('click', (e) => { + let loading = false; + this.addEventListener('click', async(e) => { cancelEvent(e); + if(loading) return; //console.log('avatar clicked'); const peerID = this.peerID; - appProfileManager.getFullPhoto(this.peerID).then(photo => { - if(this.peerID != peerID) return; - if(photo) { + loading = true; + + const photo = await appProfileManager.getFullPhoto(this.peerID); + if(this.peerID != peerID || !photo) { + loading = false; + return; + } + + if(peerID < 0) { + const maxID = Number.MAX_SAFE_INTEGER; + const inputFilter = 'inputMessagesFilterChatPhotos'; + const mid = await appMessagesManager.getSearch(peerID, '', {_: inputFilter}, maxID, 2, 0, 1).then(value => { + //console.log(lol); + // ! by descend + return value.history[0]; + }); + + if(mid) { + // ! гений в деле, костылируем (но это гениально) + let message = appMessagesManager.getMessage(mid); + const messagePhoto = message.action.photo; + if(messagePhoto.id != photo.id) { + message = { + _: 'message', + mid: maxID, + media: { + _: 'messageMediaPhoto', + photo: photo + }, + fromID: peerID + }; + + appMessagesManager.messagesStorage[maxID] = message; + } + const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji')); - new AppMediaViewerAvatar().openMedia(peerID, good ? this : null); + new AppMediaViewer(inputFilter).openMedia(message, good ? this : null); + loading = false; + return; } - }); + } + + if(photo) { + const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji')); + new AppMediaViewerAvatar(peerID).openMedia(photo.id, good ? this : null); + } + + loading = false; }); } } diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index fbf22115..98ccb5d4 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -448,7 +448,8 @@ type MyInputMessagesFilter = 'inputMessagesFilterEmpty' | 'inputMessagesFilterRoundVideo' | 'inputMessagesFilterMusic' | 'inputMessagesFilterUrl' - | 'inputMessagesFilterMyMentions'; + | 'inputMessagesFilterMyMentions' + | 'inputMessagesFilterChatPhotos'; export class AppMessagesManager { public messagesStorage: {[mid: string]: any} = {}; @@ -2314,13 +2315,14 @@ export class AppMessagesManager { } if(apiMessage.action) { - let migrateFrom; - let migrateTo; + let migrateFrom: number; + let migrateTo: number; switch(apiMessage.action._) { + //case 'messageActionChannelEditPhoto': case 'messageActionChatEditPhoto': apiMessage.action.photo = appPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); //appPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); - if(isBroadcast) { + if(isBroadcast) { // ! messageActionChannelEditPhoto не существует в принципе, это используется для перевода. apiMessage.action._ = 'messageActionChannelEditPhoto'; } break; @@ -2947,6 +2949,10 @@ export class AppMessagesManager { neededContents['url'] = true; break; + case 'inputMessagesFilterChatPhotos': + neededContents['avatar'] = true; + break; + /* case 'inputMessagesFilterMyMentions': neededContents['mentioned'] = true; break; */ @@ -2975,6 +2981,8 @@ export class AppMessagesManager { found = true; } else if(neededContents['url'] && message.message && RichTextProcessor.matchUrl(message.message)) { found = true; + } else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) { + found = true; } if(found) { @@ -4402,6 +4410,8 @@ export class AppMessagesManager { } else if(this.needSingleMessages.indexOf(msgID) == -1) { this.needSingleMessages.push(msgID); return this.fetchSingleMessages(); + } else if(this.fetchSingleMessagesPromise) { + return this.fetchSingleMessagesPromise; } } diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index c052b275..2fe598f3 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -1,12 +1,14 @@ import { CancellablePromise } from "../../helpers/cancellablePromise"; import { isSafari } from "../../helpers/userAgent"; -import { FileLocation, InputFileLocation, Photo, PhotoSize } from "../../layer"; +import { FileLocation, InputFileLocation, Photo, PhotoSize, PhotosPhotos } from "../../layer"; import { bytesFromHex, getFileNameByLocation } from "../bin_utils"; import { DownloadOptions } from "../mtproto/apiFileManager"; +import apiManager from "../mtproto/mtprotoworker"; import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; import { calcImageInBox, isObject, safeReplaceArrayInObject } from "../utils"; import { MyDocument } from "./appDocsManager"; import appDownloadManager from "./appDownloadManager"; +import appUsersManager from "./appUsersManager"; export type MyPhoto = Photo.photo; @@ -108,29 +110,27 @@ export class AppPhotosManager { return bestPhotoSize; } - /* public getUserPhotos(userID: number, maxID: number, limit: number) { - var inputUser = appUsersManager.getUserInput(userID); + public getUserPhotos(userID: number, maxID: string = '0', limit: number = 20) { + const inputUser = appUsersManager.getUserInput(userID); return apiManager.invokeApi('photos.getUserPhotos', { user_id: inputUser, offset: 0, - limit: limit || 20, - max_id: maxID || 0 - }).then((photosResult: any) => { + limit: limit, + max_id: maxID + }).then((photosResult) => { appUsersManager.saveApiUsers(photosResult.users); - var photoIDs = []; - var context = {user_id: userID}; - for(var i = 0; i < photosResult.photos.length; i++) { - //this.savePhoto(photosResult.photos[i], context); - photosResult.photos[i] = this.savePhoto(photosResult.photos[i], context); - photoIDs.push(photosResult.photos[i].id); - } + const photoIDs: string[] = []; + photosResult.photos.forEach((photo, idx) => { + photosResult.photos[idx] = this.savePhoto(photo, {type: 'profilePhoto', peerID: userID}); + photoIDs.push(photo.id); + }); return { - count: photosResult.count || photosResult.photos.length, + count: (photosResult as PhotosPhotos.photosPhotosSlice).count || photosResult.photos.length, photos: photoIDs }; }); - } */ + } public getPreviewURLFromBytes(bytes: Uint8Array | number[], isSticker = false) { let arr: Uint8Array;