Browse Source

Optimize avatars

master
Eduard Kuzmenko 3 years ago
parent
commit
4350adcfed
  1. 13
      src/components/appMediaViewerBase.ts
  2. 12
      src/components/appSelectPeers.ts
  3. 102
      src/components/avatar.ts
  4. 6
      src/components/call/index.ts
  5. 6
      src/components/chat/autocompletePeerHelper.ts
  6. 27
      src/components/chat/bubbles.ts
  7. 10
      src/components/chat/input.ts
  8. 6
      src/components/chat/topbar.ts
  9. 2
      src/components/editPeer.ts
  10. 6
      src/components/peerProfile.ts
  11. 2
      src/components/popups/createContact.ts
  12. 2
      src/components/popups/joinChatInvite.ts
  13. 8
      src/components/popups/peer.ts
  14. 10
      src/components/sidebarLeft/index.ts
  15. 14
      src/components/stackedAvatars.ts
  16. 5
      src/lib/appManagers/appAvatarsManager.ts
  17. 12
      src/lib/appManagers/appDialogsManager.ts

13
src/components/appMediaViewerBase.ts

@ -1133,14 +1133,11 @@ export default class AppMediaViewerBase< @@ -1133,14 +1133,11 @@ export default class AppMediaViewerBase<
let oldAvatar = this.author.avatarEl;
this.author.avatarEl = (oldAvatar.cloneNode() as AvatarElement);
if(!isPeerId) {
this.author.avatarEl.setAttribute('peer-title', '' + fromId);
} else {
this.author.avatarEl.removeAttribute('peer-title');
}
this.author.avatarEl.setAttribute('peer', '' + (fromId || NULL_PEER_ID));
(this.author.avatarEl as AvatarElement).updateWithOptions({
// @ts-ignore
peerId: fromId || NULL_PEER_ID,
peerTitle: isPeerId ? undefined : '' + fromId
});
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
}

12
src/components/appSelectPeers.ts

@ -28,6 +28,7 @@ import filterUnique from "../helpers/array/filterUnique"; @@ -28,6 +28,7 @@ import filterUnique from "../helpers/array/filterUnique";
import indexOfAndSplice from "../helpers/array/indexOfAndSplice";
import safeAssign from "../helpers/object/safeAssign";
import findAndSplice from "../helpers/array/findAndSplice";
import AvatarElement from "./avatar";
type SelectSearchPeerType = 'contacts' | 'dialogs' | 'channelParticipants';
@ -553,10 +554,9 @@ export default class AppSelectPeers { @@ -553,10 +554,9 @@ export default class AppSelectPeers {
const div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
const avatarEl = document.createElement('avatar-element');
avatarEl.classList.add('selector-user-avatar', 'tgico');
avatarEl.setAttribute('dialog', '1');
avatarEl.classList.add('avatar-32');
const avatarEl = new AvatarElement();
avatarEl.classList.add('selector-user-avatar', 'tgico', 'avatar-32');
avatarEl.isDialog = true;
div.dataset.key = '' + key;
if(key.isPeerId()) {
@ -564,7 +564,9 @@ export default class AppSelectPeers { @@ -564,7 +564,9 @@ export default class AppSelectPeers {
title = new PeerTitle({peerId: key.toPeerId(), dialog: true}).element;
}
avatarEl.setAttribute('peer', '' + key);
avatarEl.updateWithOptions({
peerId: key as PeerId
});
}
if(title) {

102
src/components/avatar.ts

@ -16,12 +16,12 @@ import { cancelEvent } from "../helpers/dom/cancelEvent"; @@ -16,12 +16,12 @@ 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";
import isObject from "../helpers/object/isObject";
import { ArgumentTypes } from "../types";
const onAvatarUpdate = (peerId: PeerId) => {
appAvatarsManager.removeFromAvatarsCache(peerId);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerId + '"]')) as AvatarElement[]).forEach(elem => {
(Array.from(document.querySelectorAll('avatar-element[data-peer-id="' + peerId + '"]')) as AvatarElement[]).forEach(elem => {
//console.log('updating avatar:', elem);
elem.update();
});
@ -118,34 +118,14 @@ const believeMe: Map<PeerId, Set<AvatarElement>> = new Map(); @@ -118,34 +118,14 @@ 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: boolean;
private peerTitle: string;
public peerId: PeerId;
public isDialog: boolean;
public peerTitle: string;
public loadPromises: Promise<any>[];
public lazyLoadQueue: LazyLoadQueueIntersector;
public isBig: boolean;
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() {
// браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
@ -162,38 +142,56 @@ export default class AvatarElement extends HTMLElement { @@ -162,38 +142,56 @@ export default class AvatarElement extends HTMLElement {
}
}
static get observedAttributes(): string[] {
return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */];
public attachClickEvent() {
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;
});
}
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);
}
public updateOptions(options: Partial<ArgumentTypes<AvatarElement['updateWithOptions']>[0]>) {
for(let i in options) {
// @ts-ignore
this[i] = options[i];
}
}
public updateWithOptions(options: {
peerId: PeerId,
isDialog?: boolean,
isBig?: boolean,
peerTitle?: string,
lazyLoadQueue?: LazyLoadQueueIntersector,
loadPromises?: Promise<any>[]
}) {
const wasPeerId = this.peerId;
this.updateOptions(options);
const newPeerId = this.peerId;
if(wasPeerId === newPeerId) {
return;
}
this.peerId = appPeersManager.getPeerMigratedTo(newPeerId) || newPeerId;
this.dataset.peerId = '' + newPeerId;
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';
}
return this.update();
}
private r(onlyThumb = false) {

6
src/components/call/index.ts

@ -108,9 +108,11 @@ export default class PopupCall extends PopupElement { @@ -108,9 +108,11 @@ export default class PopupCall extends PopupElement {
const peerId = this.peerId = this.instance.interlocutorUserId.toPeerId();
const avatar = new AvatarElement();
avatar.isBig = true;
avatar.setAttribute('peer', '' + peerId);
avatar.classList.add('avatar-full');
avatar.updateWithOptions({
isBig: true,
peerId: peerId
});
avatarContainer.append(avatar);
const title = new PeerTitle({

6
src/components/chat/autocompletePeerHelper.ts

@ -91,8 +91,10 @@ export default class AutocompletePeerHelper extends AutocompleteHelper { @@ -91,8 +91,10 @@ export default class AutocompletePeerHelper extends AutocompleteHelper {
const avatar = new AvatarElement();
avatar.classList.add('avatar-30', BASE + '-avatar', options.className + '-avatar');
avatar.setAttribute('dialog', '0');
avatar.setAttribute('peer', '' + options.peerId);
avatar.updateWithOptions({
isDialog: false,
peerId: options.peerId
});
const name = document.createElement('div');
name.classList.add(BASE + '-name', options.className + '-name');

27
src/components/chat/bubbles.ts

@ -660,7 +660,7 @@ export default class ChatBubbles { @@ -660,7 +660,7 @@ export default class ChatBubbles {
});
if(!IS_SAFARI) {
this.sliceViewportDebounced = debounce(this.sliceViewport.bind(this), 100, false, true);
// this.sliceViewportDebounced = debounce(this.sliceViewport.bind(this), 100, false, true);
}
let middleware: ReturnType<ChatBubbles['getMiddleware']>;
@ -1346,7 +1346,7 @@ export default class ChatBubbles { @@ -1346,7 +1346,7 @@ export default class ChatBubbles {
const nameDiv = findUpClassName(target, 'peer-title') || findUpTag(target, 'AVATAR-ELEMENT') || findUpAttribute(target, 'data-saved-from');
if(nameDiv && nameDiv !== bubble) {
target = nameDiv || target;
const peerIdStr = target.dataset.peerId || target.getAttribute('peer');
const peerIdStr = target.dataset.peerId || target.getAttribute('peer') || (target as AvatarElement).peerId;
const savedFrom = target.dataset.savedFrom;
if(typeof(peerIdStr) === 'string' || savedFrom) {
if(savedFrom) {
@ -3555,8 +3555,10 @@ export default class ChatBubbles { @@ -3555,8 +3555,10 @@ export default class ChatBubbles {
</div>`;
const avatarElem = new AvatarElement();
avatarElem.lazyLoadQueue = this.lazyLoadQueue;
avatarElem.setAttribute('peer', '' + contact.user_id.toPeerId());
avatarElem.updateWithOptions({
lazyLoadQueue: this.lazyLoadQueue,
peerId: contact.user_id.toPeerId()
});
avatarElem.classList.add('contact-avatar', 'avatar-54');
contactDiv.prepend(avatarElem);
@ -3709,16 +3711,13 @@ export default class ChatBubbles { @@ -3709,16 +3711,13 @@ export default class ChatBubbles {
const needAvatar = this.chat.isAnyGroup() && !isOut;
if(needAvatar) {
let avatarElem = new AvatarElement();
avatarElem.lazyLoadQueue = this.lazyLoadQueue;
avatarElem.classList.add('user-avatar', 'avatar-40');
avatarElem.loadPromises = loadPromises;
if(!fwdFromId && fwdFrom && fwdFrom.from_name) {
avatarElem.setAttribute('peer-title', /* '🔥 FF 🔥' */fwdFrom.from_name);
}
avatarElem.setAttribute('peer', '' + (((fwdFrom && (this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID)) || isForwardFromChannel ? fwdFromId : message.fromId) || NULL_PEER_ID));
//avatarElem.update();
avatarElem.updateWithOptions({
lazyLoadQueue: this.lazyLoadQueue,
peerId: ((fwdFrom && (this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID)) || isForwardFromChannel ? fwdFromId : message.fromId) || NULL_PEER_ID,
peerTitle: !fwdFromId && fwdFrom && fwdFrom.from_name ? /* '🔥 FF 🔥' */fwdFrom.from_name : undefined,
loadPromises
});
//this.log('exec loadDialogPhoto', message);
@ -4401,7 +4400,7 @@ export default class ChatBubbles { @@ -4401,7 +4400,7 @@ export default class ChatBubbles {
if(IS_SAFARI) {
return;
}
// const scrollSaver = new ScrollSaver(this.scrollable, true);
// scrollSaver.save();
const slice = this.getViewportSlice();

10
src/components/chat/input.ts

@ -1414,7 +1414,7 @@ export default class ChatInput { @@ -1414,7 +1414,7 @@ export default class ChatInput {
const peerId = peerIds[idx];
const avatar = new AvatarElement();
avatar.classList.add('avatar-32', 'btn-menu-item-icon');
avatar.setAttribute('peer', '' + peerId);
avatar.updateWithOptions({peerId});
if(!idx) {
avatar.classList.add('active');
@ -1430,7 +1430,7 @@ export default class ChatInput { @@ -1430,7 +1430,7 @@ export default class ChatInput {
private updateSendAsAvatar(sendAsPeerId: PeerId, skipAnimation?: boolean) {
const previousAvatar = this.sendAsAvatar;
if(previousAvatar) {
if(+previousAvatar.getAttribute('peer') === sendAsPeerId) {
if(previousAvatar.peerId === sendAsPeerId) {
return;
}
}
@ -1442,9 +1442,11 @@ export default class ChatInput { @@ -1442,9 +1442,11 @@ export default class ChatInput {
let useRafs = skipAnimation ? 0 : 2;
const duration = skipAnimation ? 0 : SEND_AS_ANIMATION_DURATION;
const avatar = this.sendAsAvatar = new AvatarElement();
avatar.setAttribute('dialog', '0');
avatar.setAttribute('peer', '' + sendAsPeerId);
avatar.classList.add('new-message-send-as-avatar', 'avatar-30');
avatar.updateWithOptions({
isDialog: false,
peerId: sendAsPeerId
});
SetTransition(avatar, 'is-visible', true, duration, undefined, useRafs);
if(previousAvatar) {

6
src/components/chat/topbar.ts

@ -521,8 +521,7 @@ export default class ChatTopbar { @@ -521,8 +521,7 @@ export default class ChatTopbar {
public constructPeerHelpers() {
this.avatarElement = new AvatarElement();
this.avatarElement.setAttribute('dialog', '1');
//this.avatarElement.setAttribute('clickable', '');
this.avatarElement.isDialog = true;
this.avatarElement.classList.add('avatar-42', 'person-avatar');
this.subtitle = document.createElement('div');
@ -690,8 +689,7 @@ export default class ChatTopbar { @@ -690,8 +689,7 @@ export default class ChatTopbar {
const peerId = this.peerId;
if(this.avatarElement) {
this.avatarElement.setAttribute('peer', '' + peerId);
this.avatarElement.update();
this.avatarElement.updateWithOptions({peerId});
}
const isBroadcast = this.appPeersManager.isBroadcast(peerId);

2
src/components/editPeer.ts

@ -49,7 +49,7 @@ export default class EditPeer { @@ -49,7 +49,7 @@ export default class EditPeer {
if(!options.withoutAvatar) {
this.avatarElem = document.createElement('avatar-element') as AvatarElement;
this.avatarElem.classList.add('avatar-placeholder', 'avatar-' + this.avatarSize);
this.avatarElem.setAttribute('peer', '' + this.peerId);
this.avatarElem.updateWithOptions({peerId: this.peerId});
if(!options.doNotEditAvatar) {
this.avatarEdit = new AvatarEdit((_upload) => {

6
src/components/peerProfile.ts

@ -86,8 +86,8 @@ export default class PeerProfile { @@ -86,8 +86,8 @@ export default class PeerProfile {
this.avatar = new AvatarElement();
this.avatar.classList.add('profile-avatar', 'avatar-120');
this.avatar.setAttribute('dialog', '' + +this.isDialog);
this.avatar.setAttribute('clickable', '');
this.avatar.isDialog = this.isDialog;
this.avatar.attachClickEvent();
this.name = document.createElement('div');
this.name.classList.add('profile-name');
@ -306,7 +306,7 @@ export default class PeerProfile { @@ -306,7 +306,7 @@ export default class PeerProfile {
this.avatars = undefined;
}
this.avatar.setAttribute('peer', '' + this.peerId);
this.avatar.updateWithOptions({peerId: this.peerId});
this.section.content.prepend(this.avatar, this.name, this.subtitle);
}

2
src/components/popups/createContact.ts

@ -55,7 +55,7 @@ export default class PopupCreateContact extends PopupElement { @@ -55,7 +55,7 @@ export default class PopupCreateContact extends PopupElement {
const onInput = () => {
const name = nameInputField.value + ' ' + lastNameInputField.value;
// const abbr = RichTextProcessor.getAbbreviation(name);
editPeer.avatarElem.setAttribute('peer-title', name);
editPeer.avatarElem.peerTitle = name;
editPeer.avatarElem.update();
};

2
src/components/popups/joinChatInvite.ts

@ -56,8 +56,8 @@ export default class PopupJoinChatInvite extends PopupElement { @@ -56,8 +56,8 @@ export default class PopupJoinChatInvite extends PopupElement {
appChatsManager.saveApiChat(fakeChat); */
const avatarElem = new AvatarElement();
avatarElem.setAttribute('dialog', '0');
avatarElem.classList.add('avatar-100');
avatarElem.isDialog = false;
if(chatInvite.photo._ === 'photo') {
chatInvite.photo = appPhotosManager.savePhoto(chatInvite.photo);
wrapPhoto({

8
src/components/popups/peer.ts

@ -33,10 +33,12 @@ export default class PopupPeer extends PopupElement { @@ -33,10 +33,12 @@ export default class PopupPeer extends PopupElement {
super('popup-peer' + (className ? ' ' + className : ''), options.buttons && addCancelButton(options.buttons), {overlayClosable: true, ...options});
if(options.peerId) {
let avatarEl = new AvatarElement();
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + options.peerId);
const avatarEl = new AvatarElement();
avatarEl.classList.add('avatar-32');
avatarEl.updateWithOptions({
isDialog: true,
peerId: options.peerId
});
this.header.prepend(avatarEl);
}

10
src/components/sidebarLeft/index.ts

@ -48,6 +48,7 @@ import noop from "../../helpers/noop"; @@ -48,6 +48,7 @@ import noop from "../../helpers/noop";
import { ripple } from "../ripple";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import formatNumber from "../../helpers/number/formatNumber";
import AvatarElement from "../avatar";
export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown';
@ -451,10 +452,9 @@ export class AppSidebarLeft extends SidebarSlider { @@ -451,10 +452,9 @@ export class AppSidebarLeft extends SidebarSlider {
const div = document.createElement('div');
div.classList.add('selector-user'/* , 'scale-in' */);
const avatarEl = document.createElement('avatar-element');
avatarEl.classList.add('selector-user-avatar', 'tgico');
avatarEl.setAttribute('dialog', '1');
avatarEl.classList.add('avatar-30');
const avatarEl = new AvatarElement();
avatarEl.classList.add('selector-user-avatar', 'tgico', 'avatar-30');
avatarEl.isDialog = true;
div.dataset.key = '' + key;
if(key.isPeerId()) {
@ -462,7 +462,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -462,7 +462,7 @@ export class AppSidebarLeft extends SidebarSlider {
title = new PeerTitle({peerId: key.toPeerId()}).element;
}
avatarEl.setAttribute('peer', '' + key);
avatarEl.updateWithOptions({peerId: key as PeerId});
} else {
avatarEl.classList.add('tgico-calendarfilter');
}

14
src/components/stackedAvatars.ts

@ -46,13 +46,17 @@ export default class StackedAvatars { @@ -46,13 +46,17 @@ export default class StackedAvatars {
let avatarElem = avatarContainer.firstElementChild as AvatarElement;
if(!avatarElem) {
avatarElem = new AvatarElement();
avatarElem.setAttribute('dialog', '0');
avatarElem.classList.add('avatar-' + this.avatarSize, AVATAR_CLASS_NAME);
avatarElem.lazyLoadQueue = this.lazyLoadQueue;
avatarElem.loadPromises = loadPromises;
avatarElem.updateOptions({
isDialog: false,
loadPromises
});
}
avatarElem.setAttribute('peer', '' + peerId);
avatarElem.updateWithOptions({
lazyLoadQueue: this.lazyLoadQueue,
peerId: peerId
});
if(!avatarElem.parentNode) {
avatarContainer.append(avatarElem);

5
src/lib/appManagers/appAvatarsManager.ts

@ -9,6 +9,7 @@ import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl" @@ -9,6 +9,7 @@ import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"
import replaceContent from "../../helpers/dom/replaceContent";
import sequentialDom from "../../helpers/sequentialDom";
import { UserProfilePhoto, ChatPhoto, InputFileLocation } from "../../layer";
import { DownloadOptions } from "../mtproto/apiFileManager";
import { NULL_PEER_ID, REPLIES_PEER_ID } from "../mtproto/mtproto_config";
import RichTextProcessor from "../richtextprocessor";
import rootScope from "../rootScope";
@ -55,12 +56,12 @@ export class AppAvatarsManager { @@ -55,12 +56,12 @@ export class AppAvatarsManager {
photo_id: photo.photo_id
};
const downloadOptions: DownloadOptions = {dcId: photo.dc_id, location: peerPhotoFileLocation};
if(size === 'photo_big') {
peerPhotoFileLocation.pFlags.big = true;
downloadOptions.limitPart = 512 * 1024;
}
const downloadOptions = {dcId: photo.dc_id, location: peerPhotoFileLocation};
/* let str: string;
const time = Date.now();
if(peerId === 0) {

12
src/lib/appManagers/appDialogsManager.ts

@ -1812,12 +1812,14 @@ export class AppDialogsManager { @@ -1812,12 +1812,14 @@ export class AppDialogsManager {
const peerId = dialog.peerId;
const avatarEl = new AvatarElement();
avatarEl.loadPromises = loadPromises;
avatarEl.lazyLoadQueue = lazyLoadQueue;
avatarEl.setAttribute('dialog', meAsSaved ? '1' : '0');
if(fromName !== undefined) avatarEl.setAttribute('peer-title', fromName);
avatarEl.setAttribute('peer', '' + peerId);
avatarEl.classList.add('dialog-avatar', 'avatar-' + avatarSize);
avatarEl.updateWithOptions({
loadPromises,
lazyLoadQueue,
isDialog: !!meAsSaved,
peerId,
peerTitle: fromName
});
if(drawStatus && peerId !== rootScope.myId && peerId.isUser()) {
const user = appUsersManager.getUser(peerId);

Loading…
Cancel
Save