tweb/src/components/peerProfileAvatars.ts

418 lines
14 KiB
TypeScript
Raw Normal View History

2021-10-27 21:16:01 +02:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
2022-08-04 08:49:54 +02:00
import IS_PARALLAX_SUPPORTED from '../environment/parallaxSupport';
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import findAndSplice from '../helpers/array/findAndSplice';
import cancelEvent from '../helpers/dom/cancelEvent';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import filterChatPhotosMessages from '../helpers/filterChatPhotosMessages';
import ListenerSetter from '../helpers/listenerSetter';
import ListLoader from '../helpers/listLoader';
import {fastRaf} from '../helpers/schedulers';
import {Message, ChatFull, MessageAction, Photo} from '../layer';
import type {AppMessagesManager} from '../lib/appManagers/appMessagesManager';
import {AppManagers} from '../lib/appManagers/managers';
import choosePhotoSize from '../lib/appManagers/utils/photos/choosePhotoSize';
import {openAvatarViewer} from './avatar';
import {putAvatar} from './putPhoto';
import Scrollable from './scrollable';
import SwipeHandler from './swipeHandler';
2022-09-20 19:34:08 +02:00
import wrapPhoto from './wrappers/photo';
2021-10-27 21:16:01 +02:00
const LOAD_NEAREST = 3;
2021-10-05 22:40:07 +02:00
export default class PeerProfileAvatars {
private static BASE_CLASS = 'profile-avatars';
private static SCALE = IS_PARALLAX_SUPPORTED ? 2 : 1;
private static TRANSLATE_TEMPLATE = IS_PARALLAX_SUPPORTED ? `translate3d({x}, 0, -1px) scale(${PeerProfileAvatars.SCALE})` : 'translate({x}, 0)';
2021-10-05 22:40:07 +02:00
public container: HTMLElement;
public avatars: HTMLElement;
public gradient: HTMLElement;
public info: HTMLElement;
public arrowPrevious: HTMLElement;
public arrowNext: HTMLElement;
private tabs: HTMLDivElement;
2021-10-21 15:16:43 +02:00
private listLoader: ListLoader<Photo.photo['id'] | Message.messageService, Photo.photo['id'] | Message.messageService>;
private peerId: PeerId;
2021-10-27 21:16:01 +02:00
private intersectionObserver: IntersectionObserver;
2022-02-22 14:53:27 +01:00
private loadCallbacks: Map<Element, () => void>;
private listenerSetter: ListenerSetter;
private swipeHandler: SwipeHandler;
2021-10-05 22:40:07 +02:00
2022-04-25 16:54:30 +02:00
constructor(
public scrollable: Scrollable,
private managers: AppManagers
) {
2021-10-05 22:40:07 +02:00
this.container = document.createElement('div');
this.container.classList.add(PeerProfileAvatars.BASE_CLASS + '-container');
this.avatars = document.createElement('div');
this.avatars.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatars');
this.gradient = document.createElement('div');
this.gradient.classList.add(PeerProfileAvatars.BASE_CLASS + '-gradient');
this.info = document.createElement('div');
this.info.classList.add(PeerProfileAvatars.BASE_CLASS + '-info');
this.tabs = document.createElement('div');
this.tabs.classList.add(PeerProfileAvatars.BASE_CLASS + '-tabs');
this.arrowPrevious = document.createElement('div');
2021-10-21 15:16:43 +02:00
this.arrowPrevious.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow', 'tgico-avatarprevious');
2021-10-05 22:40:07 +02:00
/* const previousIcon = document.createElement('i');
previousIcon.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow-icon', 'tgico-previous');
this.arrowBack.append(previousIcon); */
2022-08-04 08:49:54 +02:00
2021-10-05 22:40:07 +02:00
this.arrowNext = document.createElement('div');
2021-10-21 15:16:43 +02:00
this.arrowNext.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow', PeerProfileAvatars.BASE_CLASS + '-arrow-next', 'tgico-avatarnext');
2021-10-05 22:40:07 +02:00
/* const nextIcon = document.createElement('i');
nextIcon.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow-icon', 'tgico-next');
this.arrowNext.append(nextIcon); */
this.container.append(this.avatars, this.gradient, this.info, this.tabs, this.arrowPrevious, this.arrowNext);
2022-02-22 14:53:27 +01:00
this.loadCallbacks = new Map();
this.listenerSetter = new ListenerSetter();
2021-10-05 22:40:07 +02:00
const checkScrollTop = () => {
if(this.scrollable.scrollTop !== 0) {
this.scrollable.scrollIntoViewNew({
2022-08-04 08:49:54 +02:00
element: this.scrollable.container.firstElementChild as HTMLElement,
position: 'start'
});
2021-10-05 22:40:07 +02:00
return false;
}
return true;
};
const SWITCH_ZONE = 1 / 3;
let cancel = false;
let freeze = false;
attachClickEvent(this.container, async(_e) => {
if(freeze) {
cancelEvent(_e);
return;
}
if(cancel) {
cancel = false;
return;
}
if(!checkScrollTop()) {
return;
}
const rect = this.container.getBoundingClientRect();
// const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
const e = _e;
const x = e.pageX;
const clickX = x - rect.left;
2022-08-04 08:49:54 +02:00
if((!this.listLoader.previous.length && !this.listLoader.next.length) ||
(clickX > (rect.width * SWITCH_ZONE) && clickX < (rect.width - rect.width * SWITCH_ZONE))) {
2021-10-05 22:40:07 +02:00
const peerId = this.peerId;
2021-10-21 15:16:43 +02:00
const targets: {element: HTMLElement, item: Photo.photo['id'] | Message.messageService}[] = [];
2021-10-05 22:40:07 +02:00
this.listLoader.previous.concat(this.listLoader.current, this.listLoader.next).forEach((item, idx) => {
targets.push({
element: /* null */this.avatars.children[idx] as HTMLElement,
item
});
});
const prevTargets = targets.slice(0, this.listLoader.previous.length);
const nextTargets = targets.slice(this.listLoader.previous.length + 1);
const target = this.avatars.children[this.listLoader.previous.length] as HTMLElement;
freeze = true;
openAvatarViewer(target, peerId, () => peerId === this.peerId, this.listLoader.current, prevTargets, nextTargets);
freeze = false;
} else {
const centerX = rect.right - (rect.width / 2);
const toRight = x > centerX;
2022-08-04 08:49:54 +02:00
2021-10-05 22:40:07 +02:00
// this.avatars.classList.remove('no-transition');
// fastRaf(() => {
2022-08-04 08:49:54 +02:00
this.avatars.classList.add('no-transition');
void this.avatars.offsetLeft; // reflow
2021-10-05 22:40:07 +02:00
2022-08-04 08:49:54 +02:00
let distance: number;
if(this.listLoader.index === 0 && !toRight) distance = this.listLoader.count - 1;
else if(this.listLoader.index === (this.listLoader.count - 1) && toRight) distance = -(this.listLoader.count - 1);
else distance = toRight ? 1 : -1;
this.listLoader.go(distance);
2021-10-05 22:40:07 +02:00
2022-08-04 08:49:54 +02:00
fastRaf(() => {
this.avatars.classList.remove('no-transition');
});
2021-10-05 22:40:07 +02:00
// });
}
2022-02-22 14:53:27 +01:00
}, {listenerSetter: this.listenerSetter});
2021-10-05 22:40:07 +02:00
const cancelNextClick = () => {
cancel = true;
document.body.addEventListener(IS_TOUCH_SUPPORTED ? 'touchend' : 'click', (e) => {
cancel = false;
}, {once: true});
};
2022-08-04 08:49:54 +02:00
let width = 0, x = 0, lastDiffX = 0, /* lastIndex = 0, */ minX = 0;
2022-02-22 14:53:27 +01:00
const swipeHandler = this.swipeHandler = new SwipeHandler({
2022-08-04 08:49:54 +02:00
element: this.avatars,
2021-10-05 22:40:07 +02:00
onSwipe: (xDiff, yDiff) => {
2023-01-13 15:50:48 +01:00
xDiff *= -1;
yDiff *= -1;
2021-10-05 22:40:07 +02:00
lastDiffX = xDiff;
let lastX = x + xDiff * -PeerProfileAvatars.SCALE;
if(lastX > 0) lastX = 0;
else if(lastX < minX) lastX = minX;
this.avatars.style.transform = PeerProfileAvatars.TRANSLATE_TEMPLATE.replace('{x}', lastX + 'px');
2022-08-04 08:49:54 +02:00
// console.log(xDiff, yDiff);
2021-10-05 22:40:07 +02:00
return false;
2022-08-04 08:49:54 +02:00
},
2021-10-05 22:40:07 +02:00
verifyTouchTarget: (e) => {
if(!checkScrollTop()) {
cancelNextClick();
2023-01-13 15:50:48 +01:00
cancelEvent(e as any as Event);
2021-10-05 22:40:07 +02:00
return false;
} else if(this.container.classList.contains('is-single') || freeze) {
return false;
}
return true;
2022-08-04 08:49:54 +02:00
},
2021-10-05 22:40:07 +02:00
onFirstSwipe: () => {
const rect = this.avatars.getBoundingClientRect();
width = rect.width;
minX = -width * (this.tabs.childElementCount - 1);
/* lastIndex = whichChild(this.tabs.querySelector('.active'));
x = -width * lastIndex; */
x = rect.left - this.container.getBoundingClientRect().left;
2022-08-04 08:49:54 +02:00
2021-10-05 22:40:07 +02:00
this.avatars.style.transform = PeerProfileAvatars.TRANSLATE_TEMPLATE.replace('{x}', x + 'px');
this.container.classList.add('is-swiping');
this.avatars.classList.add('no-transition');
void this.avatars.offsetLeft; // reflow
},
onReset: () => {
const addIndex = Math.ceil(Math.abs(lastDiffX) / (width / PeerProfileAvatars.SCALE)) * (lastDiffX >= 0 ? 1 : -1);
cancelNextClick();
2022-08-04 08:49:54 +02:00
// console.log(addIndex);
2021-10-05 22:40:07 +02:00
this.avatars.classList.remove('no-transition');
fastRaf(() => {
this.listLoader.go(addIndex);
this.container.classList.remove('is-swiping');
});
}
});
2021-10-27 21:16:01 +02:00
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
2021-10-27 21:16:01 +02:00
if(!entry.isIntersecting) {
return;
}
this.loadNearestToTarget(entry.target);
});
});
2022-02-22 14:53:27 +01:00
/* this.listenerSetter.add(rootScope)('avatar_update', (peerId) => {
if(this.peerId === peerId) {
const photo = appPeersManager.getPeerPhoto(peerId);
if(photo) {
const id = photo.photo_id;
const previous = this.listLoader.previous;
for(let i = 0; i < previous.length; ++i) {
if(previous[i] === id)
}
this.listLoader.previous.forEach((_id, idx, arr) => {});
}
}
}); */
2021-10-05 22:40:07 +02:00
}
public async setPeer(peerId: PeerId) {
2021-10-05 22:40:07 +02:00
this.peerId = peerId;
const photo = await this.managers.appPeersManager.getPeerPhoto(peerId);
2021-10-05 22:40:07 +02:00
if(!photo) {
return;
}
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader({
loadCount: 50,
loadMore: (anchor, older, loadCount) => {
if(!older) return Promise.resolve({count: undefined, items: []});
2021-10-21 15:16:43 +02:00
if(peerId.isUser()) {
const maxId: Photo.photo['id'] = anchor as any;
return this.managers.appPhotosManager.getUserPhotos(peerId, maxId, loadCount).then((value) => {
2021-10-05 22:40:07 +02:00
return {
count: value.count,
items: value.photos
};
});
} else {
const promises: [Promise<ChatFull> | ChatFull, ReturnType<AppMessagesManager['getSearch']>] = [] as any;
2021-10-05 22:40:07 +02:00
if(!listLoader.current) {
promises.push(this.managers.appProfileManager.getChatFull(peerId.toChatId()));
2021-10-05 22:40:07 +02:00
}
2022-08-04 08:49:54 +02:00
2022-04-25 16:54:30 +02:00
promises.push(this.managers.appMessagesManager.getSearch({
2021-10-05 22:40:07 +02:00
peerId,
maxId: Number.MAX_SAFE_INTEGER,
inputFilter: {
_: 'inputMessagesFilterChatPhotos'
},
limit: loadCount,
backLimit: 0
}));
return Promise.all(promises).then(async(result) => {
2021-10-05 22:40:07 +02:00
const value = result.pop() as typeof result[1];
filterChatPhotosMessages(value);
if(!listLoader.current) {
const chatFull = result[0];
const message = findAndSplice(value.history, (message) => {
return ((message as Message.messageService).action as MessageAction.messageActionChannelEditPhoto).photo.id === chatFull.chat_photo.id;
2021-10-05 22:40:07 +02:00
}) as Message.messageService;
2022-08-04 08:49:54 +02:00
listLoader.current = message || await this.managers.appMessagesManager.generateFakeAvatarMessage(this.peerId, chatFull.chat_photo);
2021-10-05 22:40:07 +02:00
}
2022-08-04 08:49:54 +02:00
// console.log('avatars loaded:', value);
2021-10-05 22:40:07 +02:00
return {
count: value.count,
items: value.history
};
});
}
},
processItem: this.processItem,
onJump: (item, older) => {
const id = this.listLoader.index;
2022-08-04 08:49:54 +02:00
// const nextId = Math.max(0, id);
2021-10-05 22:40:07 +02:00
const x = 100 * PeerProfileAvatars.SCALE * id;
this.avatars.style.transform = PeerProfileAvatars.TRANSLATE_TEMPLATE.replace('{x}', `-${x}%`);
const activeTab = this.tabs.querySelector('.active');
if(activeTab) activeTab.classList.remove('active');
const tab = this.tabs.children[id] as HTMLElement;
tab.classList.add('active');
2021-10-27 21:16:01 +02:00
this.loadNearestToTarget(this.avatars.children[id]);
2021-10-05 22:40:07 +02:00
}
});
if(photo._ === 'userProfilePhoto') {
listLoader.current = photo.photo_id;
}
await this.processItem(listLoader.current);
2021-10-05 22:40:07 +02:00
// listLoader.loaded
listLoader.load(true);
}
public addTab() {
const tab = document.createElement('div');
tab.classList.add(PeerProfileAvatars.BASE_CLASS + '-tab');
this.tabs.append(tab);
if(this.tabs.childElementCount === 1) {
tab.classList.add('active');
}
this.container.classList.toggle('is-single', this.tabs.childElementCount <= 1);
}
public processItem = async(photoId: Photo.photo['id'] | Message.messageService) => {
2021-10-05 22:40:07 +02:00
const avatar = document.createElement('div');
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar', 'media-container', 'hide');
this.avatars.append(avatar);
2021-10-05 22:40:07 +02:00
let photo: Photo.photo;
if(photoId) {
2022-08-04 08:49:54 +02:00
photo = typeof(photoId) !== 'object' ?
await this.managers.appPhotosManager.getPhoto(photoId) :
2021-10-05 22:40:07 +02:00
(photoId.action as MessageAction.messageActionChannelEditPhoto).photo as Photo.photo;
}
const img = new Image();
2021-10-27 21:16:01 +02:00
img.classList.add('avatar-photo');
2021-10-05 22:40:07 +02:00
img.draggable = false;
const loadCallback = async() => {
2021-10-27 21:16:01 +02:00
if(photo) {
const res = await wrapPhoto({
2021-10-27 21:16:01 +02:00
container: avatar,
photo,
2022-04-25 16:54:30 +02:00
size: choosePhotoSize(photo, 420, 420, false),
2021-10-27 21:16:01 +02:00
withoutPreloader: true
2021-10-05 22:40:07 +02:00
});
2022-08-04 08:49:54 +02:00
[res.images.thumb, res.images.full].filter(Boolean).forEach((img) => {
2021-10-27 21:16:01 +02:00
img.classList.add('avatar-photo');
});
} else {
const photo = await this.managers.appPeersManager.getPeerPhoto(this.peerId);
await putAvatar(avatar, this.peerId, photo, 'photo_big', img);
2021-10-27 21:16:01 +02:00
}
avatar.classList.remove('hide');
2021-10-27 21:16:01 +02:00
};
if(this.avatars.childElementCount <= LOAD_NEAREST) {
await loadCallback();
2021-10-05 22:40:07 +02:00
} else {
2021-10-27 21:16:01 +02:00
this.intersectionObserver.observe(avatar);
this.loadCallbacks.set(avatar, loadCallback);
2021-10-05 22:40:07 +02:00
}
this.addTab();
return photoId;
};
2021-10-27 21:16:01 +02:00
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) => {
2021-10-27 21:16:01 +02:00
const callback = this.loadCallbacks.get(target);
if(callback) {
callback();
this.loadCallbacks.delete(target);
this.intersectionObserver.unobserve(target);
}
});
}
2022-02-22 14:53:27 +01:00
public cleanup() {
this.listenerSetter.removeAll();
this.swipeHandler.removeListeners();
2022-07-26 07:22:46 +02:00
this.intersectionObserver?.disconnect();
2022-02-22 14:53:27 +01:00
}
2021-10-05 22:40:07 +02:00
}