tweb/src/components/avatar.ts

271 lines
8.3 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope";
import { Message, Photo } from "../layer";
import appPeersManager from "../lib/appManagers/appPeersManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
import { attachClickEvent } from "../helpers/dom/clickEvent";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
import AppMediaViewer from "./appMediaViewer";
import AppMediaViewerAvatar from "./appMediaViewerAvatar";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
const onAvatarUpdate = (peerId: PeerId) => {
appAvatarsManager.removeFromAvatarsCache(peerId);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerId + '"]')) as AvatarElement[]).forEach(elem => {
//console.log('updating avatar:', elem);
elem.update();
});
};
rootScope.addEventListener('avatar_update', onAvatarUpdate);
rootScope.addEventListener('peer_title_edit', (peerId) => {
if(!appAvatarsManager.isAvatarCached(peerId)) {
onAvatarUpdate(peerId);
}
});
export async function openAvatarViewer(
target: HTMLElement,
peerId: PeerId,
middleware: () => boolean,
message?: any,
prevTargets?: {element: HTMLElement, item: Photo.photo['id'] | Message.messageService}[],
nextTargets?: typeof prevTargets
) {
let photo = await appProfileManager.getFullPhoto(peerId);
if(!middleware() || !photo) {
return;
}
const getTarget = () => {
const good = Array.from(target.querySelectorAll('img')).find(img => !img.classList.contains('emoji'));
return good ? target : null;
};
if(peerId.isAnyChat()) {
const hadMessage = !!message;
const inputFilter = 'inputMessagesFilterChatPhotos';
if(!message) {
message = await appMessagesManager.getSearch({
peerId,
inputFilter: {_: inputFilter},
maxId: 0,
limit: 1
}).then(value => {
//console.log(lol);
// ! by descend
return value.history[0];
});
if(!middleware()) {
return;
}
}
if(message) {
// ! гений в деле, костылируем (но это гениально)
const messagePhoto = message.action.photo;
if(messagePhoto.id !== photo.id) {
if(!hadMessage) {
message = appMessagesManager.generateFakeAvatarMessage(peerId, photo);
} else {
}
}
const f = (arr: typeof prevTargets) => arr.map(el => ({
element: el.element,
mid: (el.item as Message.messageService).mid,
peerId: (el.item as Message.messageService).peerId
}));
new AppMediaViewer()
.setSearchContext({
peerId,
inputFilter: {_: inputFilter},
})
.openMedia(message, getTarget(), undefined, undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);
return;
}
}
if(photo) {
if(typeof(message) === 'string') {
photo = appPhotosManager.getPhoto(message);
}
const f = (arr: typeof prevTargets) => arr.map(el => ({
element: el.element,
photoId: el.item as string
}));
new AppMediaViewerAvatar(peerId).openMedia(photo.id, getTarget(), undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);
}
}
const believeMe: Map<PeerId, Set<AvatarElement>> = new Map();
const seen: Set<PeerId> = new Set();
export default class AvatarElement extends HTMLElement {
private peerId: PeerId;
private isDialog = false;
private peerTitle: string;
public loadPromises: Promise<any>[];
public lazyLoadQueue: LazyLoadQueueIntersector;
private addedToQueue = false;
connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
this.isDialog = this.getAttribute('dialog') === '1';
if(this.getAttribute('clickable') === '') {
this.setAttribute('clickable', 'set');
let loading = false;
attachClickEvent(this, async(e) => {
cancelEvent(e);
if(loading) return;
//console.log('avatar clicked');
const peerId = this.peerId;
loading = true;
await openAvatarViewer(this, this.peerId, () => this.peerId === peerId);
loading = false;
});
}
}
disconnectedCallback() {
// браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
const set = believeMe.get(this.peerId);
if(set && set.has(this)) {
set.delete(this);
if(!set.size) {
believeMe.delete(this.peerId);
}
}
if(this.lazyLoadQueue) {
this.lazyLoadQueue.unobserve(this);
}
}
static get observedAttributes(): string[] {
return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
//console.log('avatar changed attribute:', name, oldValue, newValue);
// вызывается при изменении одного из перечисленных выше атрибутов
if(name === 'peer') {
const newPeerId = (newValue || '').toPeerId() || NULL_PEER_ID;
if(this.peerId === newPeerId) {
return;
}
this.peerId = appPeersManager.getPeerMigratedTo(newPeerId) || newPeerId;
const wasPeerId = (oldValue || '').toPeerId() || NULL_PEER_ID;
if(wasPeerId) {
const set = believeMe.get(wasPeerId);
if(set) {
set.delete(this);
if(!set.size) {
believeMe.delete(wasPeerId);
}
}
}
this.update();
} else if(name === 'peer-title') {
this.peerTitle = newValue;
} else if(name === 'dialog') {
this.isDialog = newValue === '1';
}
}
private r(onlyThumb = false) {
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb);
const promise = res ? res.loadPromise : Promise.resolve();
if(this.loadPromises) {
if(res && res.cached) {
this.loadPromises.push(promise);
}
promise.finally(() => {
this.loadPromises = undefined;
});
}
return res;
}
public update() {
if(this.lazyLoadQueue) {
if(!seen.has(this.peerId)) {
if(this.addedToQueue) return;
this.addedToQueue = true;
let set = believeMe.get(this.peerId);
if(!set) {
set = new Set();
believeMe.set(this.peerId, set);
}
set.add(this);
this.r(true);
this.lazyLoadQueue.push({
div: this,
load: () => {
seen.add(this.peerId);
return this.update();
}
});
return;
} else if(this.addedToQueue) {
this.lazyLoadQueue.unobserve(this);
}
}
seen.add(this.peerId);
const res = this.r();
const promise = res ? res.loadPromise : Promise.resolve();
if(this.addedToQueue) {
promise.finally(() => {
this.addedToQueue = false;
});
}
const set = believeMe.get(this.peerId);
if(set) {
set.delete(this);
const arr = Array.from(set);
believeMe.delete(this.peerId);
for(let i = 0, length = arr.length; i < length; ++i) {
arr[i].update();
}
}
return promise;
}
}
customElements.define('avatar-element', AvatarElement);