Better profile avatar loading
This commit is contained in:
parent
1d388fe62a
commit
7e7eb78a8a
@ -1,14 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
import PARALLAX_SUPPORTED from "../environment/parallaxSupport";
|
import PARALLAX_SUPPORTED from "../environment/parallaxSupport";
|
||||||
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
|
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
|
||||||
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
||||||
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
||||||
import renderImageFromUrl from "../helpers/dom/renderImageFromUrl";
|
|
||||||
import filterChatPhotosMessages from "../helpers/filterChatPhotosMessages";
|
import filterChatPhotosMessages from "../helpers/filterChatPhotosMessages";
|
||||||
import ListLoader from "../helpers/listLoader";
|
import ListLoader from "../helpers/listLoader";
|
||||||
import { fastRaf } from "../helpers/schedulers";
|
import { fastRaf } from "../helpers/schedulers";
|
||||||
import { Message, ChatFull, MessageAction, Photo } from "../layer";
|
import { Message, ChatFull, MessageAction, Photo } from "../layer";
|
||||||
import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
|
import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
|
||||||
import appDownloadManager from "../lib/appManagers/appDownloadManager";
|
|
||||||
import appMessagesManager, { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
|
import appMessagesManager, { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
|
||||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||||
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
||||||
@ -16,6 +20,9 @@ import appProfileManager from "../lib/appManagers/appProfileManager";
|
|||||||
import { openAvatarViewer } from "./avatar";
|
import { openAvatarViewer } from "./avatar";
|
||||||
import Scrollable from "./scrollable";
|
import Scrollable from "./scrollable";
|
||||||
import SwipeHandler from "./swipeHandler";
|
import SwipeHandler from "./swipeHandler";
|
||||||
|
import { wrapPhoto } from "./wrappers";
|
||||||
|
|
||||||
|
const LOAD_NEAREST = 3;
|
||||||
|
|
||||||
export default class PeerProfileAvatars {
|
export default class PeerProfileAvatars {
|
||||||
private static BASE_CLASS = 'profile-avatars';
|
private static BASE_CLASS = 'profile-avatars';
|
||||||
@ -30,6 +37,8 @@ export default class PeerProfileAvatars {
|
|||||||
private tabs: HTMLDivElement;
|
private tabs: HTMLDivElement;
|
||||||
private listLoader: ListLoader<Photo.photo['id'] | Message.messageService, Photo.photo['id'] | Message.messageService>;
|
private listLoader: ListLoader<Photo.photo['id'] | Message.messageService, Photo.photo['id'] | Message.messageService>;
|
||||||
private peerId: PeerId;
|
private peerId: PeerId;
|
||||||
|
private intersectionObserver: IntersectionObserver;
|
||||||
|
private loadCallbacks: Map<Element, () => void> = new Map();
|
||||||
|
|
||||||
constructor(public scrollable: Scrollable) {
|
constructor(public scrollable: Scrollable) {
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
@ -197,6 +206,16 @@ export default class PeerProfileAvatars {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.intersectionObserver = new IntersectionObserver(entries => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if(!entry.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadNearestToTarget(entry.target);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setPeer(peerId: PeerId) {
|
public setPeer(peerId: PeerId) {
|
||||||
@ -270,6 +289,8 @@ export default class PeerProfileAvatars {
|
|||||||
|
|
||||||
const tab = this.tabs.children[id] as HTMLElement;
|
const tab = this.tabs.children[id] as HTMLElement;
|
||||||
tab.classList.add('active');
|
tab.classList.add('active');
|
||||||
|
|
||||||
|
this.loadNearestToTarget(this.avatars.children[id]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -297,7 +318,7 @@ export default class PeerProfileAvatars {
|
|||||||
|
|
||||||
public processItem = (photoId: Photo.photo['id'] | Message.messageService) => {
|
public processItem = (photoId: Photo.photo['id'] | Message.messageService) => {
|
||||||
const avatar = document.createElement('div');
|
const avatar = document.createElement('div');
|
||||||
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar');
|
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar', 'media-container');
|
||||||
|
|
||||||
let photo: Photo.photo;
|
let photo: Photo.photo;
|
||||||
if(photoId) {
|
if(photoId) {
|
||||||
@ -307,20 +328,32 @@ export default class PeerProfileAvatars {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar-image');
|
img.classList.add('avatar-photo');
|
||||||
img.draggable = false;
|
img.draggable = false;
|
||||||
|
|
||||||
if(photo) {
|
const loadCallback = () => {
|
||||||
const size = appPhotosManager.choosePhotoSize(photo, 420, 420, false);
|
if(photo) {
|
||||||
appPhotosManager.preloadPhoto(photo, size).then(() => {
|
const res = wrapPhoto({
|
||||||
const cacheContext = appDownloadManager.getCacheContext(photo, size.type);
|
container: avatar,
|
||||||
renderImageFromUrl(img, cacheContext.url, () => {
|
photo,
|
||||||
avatar.append(img);
|
size: appPhotosManager.choosePhotoSize(photo, 420, 420, false),
|
||||||
|
withoutPreloader: true
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
[res.images.thumb, res.images.full].filter(Boolean).forEach(img => {
|
||||||
|
img.classList.add('avatar-photo');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const photo = appPeersManager.getPeerPhoto(this.peerId);
|
||||||
|
appAvatarsManager.putAvatar(avatar, this.peerId, photo, 'photo_big', img);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.avatars.childElementCount <= LOAD_NEAREST) {
|
||||||
|
loadCallback();
|
||||||
} else {
|
} else {
|
||||||
const photo = appPeersManager.getPeerPhoto(this.peerId);
|
this.intersectionObserver.observe(avatar);
|
||||||
appAvatarsManager.putAvatar(avatar, this.peerId, photo, 'photo_big', img);
|
this.loadCallbacks.set(avatar, loadCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.avatars.append(avatar);
|
this.avatars.append(avatar);
|
||||||
@ -329,4 +362,19 @@ export default class PeerProfileAvatars {
|
|||||||
|
|
||||||
return photoId;
|
return photoId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private loadNearestToTarget(target: Element) {
|
||||||
|
const children = Array.from(target.parentElement.children);
|
||||||
|
const idx = children.indexOf(target);
|
||||||
|
const slice = children.slice(Math.max(0, idx - LOAD_NEAREST), Math.min(children.length, idx + LOAD_NEAREST));
|
||||||
|
|
||||||
|
slice.forEach(target => {
|
||||||
|
const callback = this.loadCallbacks.get(target);
|
||||||
|
if(callback) {
|
||||||
|
callback();
|
||||||
|
this.loadCallbacks.delete(target);
|
||||||
|
this.intersectionObserver.unobserve(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,11 +84,21 @@ export class AppAvatarsManager {
|
|||||||
return {cached, loadPromise: getAvatarPromise};
|
return {cached, loadPromise: getAvatarPromise};
|
||||||
}
|
}
|
||||||
|
|
||||||
public putAvatar(div: HTMLElement, peerId: PeerId, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image(), onlyThumb = false) {
|
public putAvatar(
|
||||||
|
div: HTMLElement,
|
||||||
|
peerId: PeerId,
|
||||||
|
photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto,
|
||||||
|
size: PeerPhotoSize,
|
||||||
|
img = new Image(),
|
||||||
|
onlyThumb = false
|
||||||
|
) {
|
||||||
let {cached, loadPromise} = this.loadAvatar(peerId, photo, size);
|
let {cached, loadPromise} = this.loadAvatar(peerId, photo, size);
|
||||||
|
|
||||||
|
img.classList.add('avatar-photo');
|
||||||
|
|
||||||
let renderThumbPromise: Promise<void>;
|
let renderThumbPromise: Promise<void>;
|
||||||
let callback: () => void;
|
let callback: () => void;
|
||||||
|
let thumbImage: HTMLImageElement;
|
||||||
if(cached) {
|
if(cached) {
|
||||||
// смотри в misc.ts: renderImageFromUrl
|
// смотри в misc.ts: renderImageFromUrl
|
||||||
callback = () => {
|
callback = () => {
|
||||||
@ -101,12 +111,14 @@ export class AppAvatarsManager {
|
|||||||
img.classList.add('fade-in');
|
img.classList.add('fade-in');
|
||||||
}
|
}
|
||||||
|
|
||||||
let thumbImage: HTMLImageElement;
|
if(size === 'photo_big') { // let's load small photo first
|
||||||
if(photo.stripped_thumb) {
|
const res = this.putAvatar(div, peerId, photo, 'photo_small');
|
||||||
|
renderThumbPromise = res.loadPromise;
|
||||||
|
thumbImage = res.thumbImage;
|
||||||
|
} else if(photo.stripped_thumb) {
|
||||||
thumbImage = new Image();
|
thumbImage = new Image();
|
||||||
div.classList.add('avatar-relative');
|
div.classList.add('avatar-relative');
|
||||||
thumbImage.classList.add('avatar-photo', 'avatar-photo-thumbnail');
|
thumbImage.classList.add('avatar-photo', 'avatar-photo-thumbnail');
|
||||||
img.classList.add('avatar-photo');
|
|
||||||
const url = appPhotosManager.getPreviewURLFromBytes(photo.stripped_thumb);
|
const url = appPhotosManager.getPreviewURLFromBytes(photo.stripped_thumb);
|
||||||
renderThumbPromise = renderImageFromUrlPromise(thumbImage, url).then(() => {
|
renderThumbPromise = renderImageFromUrlPromise(thumbImage, url).then(() => {
|
||||||
replaceContent(div, thumbImage);
|
replaceContent(div, thumbImage);
|
||||||
@ -114,7 +126,7 @@ export class AppAvatarsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callback = () => {
|
callback = () => {
|
||||||
if(photo.stripped_thumb) {
|
if(thumbImage) {
|
||||||
div.append(img);
|
div.append(img);
|
||||||
} else {
|
} else {
|
||||||
replaceContent(div, img);
|
replaceContent(div, img);
|
||||||
@ -140,9 +152,13 @@ export class AppAvatarsManager {
|
|||||||
|
|
||||||
const renderPromise = loadPromise
|
const renderPromise = loadPromise
|
||||||
.then((url) => renderImageFromUrlPromise(img, url/* , false */))
|
.then((url) => renderImageFromUrlPromise(img, url/* , false */))
|
||||||
.then(() => callback());
|
.then(callback);
|
||||||
|
|
||||||
return {cached, loadPromise: renderThumbPromise || renderPromise};
|
return {
|
||||||
|
cached,
|
||||||
|
loadPromise: renderThumbPromise || renderPromise,
|
||||||
|
thumbImage
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public s(div: HTMLElement, innerHTML: string, color: string, icon: string) {
|
public s(div: HTMLElement, innerHTML: string, color: string, icon: string) {
|
||||||
|
@ -212,8 +212,10 @@ avatar-element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-photo {
|
.avatar-relative {
|
||||||
position: absolute;
|
.avatar-photo {
|
||||||
top: 0;
|
position: absolute;
|
||||||
left: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,9 @@
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&-image {
|
.avatar-photo {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
@ -99,7 +100,8 @@
|
|||||||
bottom: .5625rem;
|
bottom: .5625rem;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
.profile-name, .profile-subtitle {
|
.profile-name,
|
||||||
|
.profile-subtitle {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -322,7 +324,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-name, &-subtitle, &-avatar {
|
&-name,
|
||||||
|
&-subtitle,
|
||||||
|
&-avatar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user