Browse Source

Fix blinking avatars in chat, replies footer

master
morethanwords 4 years ago
parent
commit
cc92edeb26
  1. 33
      src/components/avatar.ts
  2. 6
      src/components/chat/bubbles.ts
  3. 7
      src/components/chat/messageRender.ts
  4. 85
      src/components/chat/replies.ts
  5. 1
      src/scss/partials/_avatar.scss

33
src/components/avatar.ts

@ -4,7 +4,7 @@ import rootScope from "../lib/rootScope";
import { attachClickEvent, cancelEvent } from "../helpers/dom"; import { attachClickEvent, cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer"; import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
import { Photo } from "../layer"; import { Photo } from "../layer";
import type { LazyLoadQueueIntersector } from "./lazyLoadQueue"; //import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
rootScope.on('avatar_update', (e) => { rootScope.on('avatar_update', (e) => {
let peerId = e; let peerId = e;
@ -20,8 +20,9 @@ export default class AvatarElement extends HTMLElement {
private peerId: number; private peerId: number;
private isDialog = false; private isDialog = false;
public peerTitle: string; public peerTitle: string;
public lazyLoadQueue: LazyLoadQueueIntersector; public loadPromises: Promise<any>[];
private addedToQueue = false; //public lazyLoadQueue: LazyLoadQueueIntersector;
//private addedToQueue = false;
constructor() { constructor() {
super(); super();
@ -105,13 +106,13 @@ export default class AvatarElement extends HTMLElement {
} }
} }
disconnectedCallback() { /* disconnectedCallback() {
// браузер вызывает этот метод при удалении элемента из документа // браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется) // (может вызываться много раз, если элемент многократно добавляется/удаляется)
if(this.lazyLoadQueue) { if(this.lazyLoadQueue) {
this.lazyLoadQueue.unobserve(this); this.lazyLoadQueue.unobserve(this);
} }
} } */
static get observedAttributes(): string[] { static get observedAttributes(): string[] {
return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */]; return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */];
@ -120,22 +121,22 @@ export default class AvatarElement extends HTMLElement {
attributeChangedCallback(name: string, oldValue: string, newValue: string) { attributeChangedCallback(name: string, oldValue: string, newValue: string) {
//console.log('avatar changed attribute:', name, oldValue, newValue); //console.log('avatar changed attribute:', name, oldValue, newValue);
// вызывается при изменении одного из перечисленных выше атрибутов // вызывается при изменении одного из перечисленных выше атрибутов
if(name == 'peer') { if(name === 'peer') {
if(this.peerId == +newValue) { if(this.peerId === +newValue) {
return; return;
} }
this.peerId = +newValue; this.peerId = +newValue;
this.update(); this.update();
} else if(name == 'peer-title') { } else if(name === 'peer-title') {
this.peerTitle = newValue; this.peerTitle = newValue;
} else if(name == 'dialog') { } else if(name === 'dialog') {
this.isDialog = !!+newValue; this.isDialog = !!+newValue;
} }
} }
public update() { public update() {
if(this.lazyLoadQueue) { /* if(this.lazyLoadQueue) {
if(this.addedToQueue) return; if(this.addedToQueue) return;
this.lazyLoadQueue.push({ this.lazyLoadQueue.push({
div: this, div: this,
@ -146,9 +147,15 @@ export default class AvatarElement extends HTMLElement {
} }
}); });
this.addedToQueue = true; this.addedToQueue = true;
} else { } else { */
appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle); const res = appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle);
} if(this.loadPromises && res && res.cached) {
this.loadPromises.push(res.loadPromise);
res.loadPromise.finally(() => {
this.loadPromises = undefined;
});
}
//}
} }
} }

6
src/components/chat/bubbles.ts

@ -2256,13 +2256,14 @@ export default class ChatBubbles {
let avatarElem = new AvatarElement(); let avatarElem = new AvatarElement();
//avatarElem.lazyLoadQueue = this.lazyLoadQueue; //avatarElem.lazyLoadQueue = this.lazyLoadQueue;
avatarElem.classList.add('user-avatar', 'avatar-40'); avatarElem.classList.add('user-avatar', 'avatar-40');
avatarElem.loadPromises = loadPromises;
if(!message.fwdFromId && message.fwd_from && message.fwd_from.from_name) { if(!message.fwdFromId && message.fwd_from && message.fwd_from.from_name) {
avatarElem.setAttribute('peer-title', /* '🔥 FF 🔥' */message.fwd_from.from_name); avatarElem.setAttribute('peer-title', /* '🔥 FF 🔥' */message.fwd_from.from_name);
} }
avatarElem.setAttribute('peer', '' + (((message.fwd_from && (this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID)) || isForwardFromChannel ? message.fwdFromId : message.fromId) || 0)); avatarElem.setAttribute('peer', '' + (((message.fwd_from && (this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID)) || isForwardFromChannel ? message.fwdFromId : message.fromId) || 0));
avatarElem.update(); //avatarElem.update();
//this.log('exec loadDialogPhoto', message); //this.log('exec loadDialogPhoto', message);
@ -2305,7 +2306,8 @@ export default class ChatBubbles {
bubble, bubble,
bubbleContainer, bubbleContainer,
message: messageWithReplies, message: messageWithReplies,
messageDiv messageDiv,
loadPromises
}); });
if(isFooter) { if(isFooter) {

7
src/components/chat/messageRender.ts

@ -58,16 +58,19 @@ export namespace MessageRender {
return timeSpan; return timeSpan;
}; };
export const renderReplies = ({bubble, bubbleContainer, message, messageDiv}: { export const renderReplies = ({bubble, bubbleContainer, message, messageDiv, loadPromises}: {
bubble: HTMLElement, bubble: HTMLElement,
bubbleContainer: HTMLElement, bubbleContainer: HTMLElement,
message: any, message: any,
messageDiv: HTMLElement messageDiv: HTMLElement,
loadPromises?: Promise<any>[]
}) => { }) => {
const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big') && !bubble.classList.contains('round'); const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big') && !bubble.classList.contains('round');
const repliesFooter = new RepliesElement(); const repliesFooter = new RepliesElement();
repliesFooter.message = message; repliesFooter.message = message;
repliesFooter.type = isFooter ? 'footer' : 'beside'; repliesFooter.type = isFooter ? 'footer' : 'beside';
repliesFooter.loadPromises = loadPromises;
repliesFooter.init();
bubbleContainer.prepend(repliesFooter); bubbleContainer.prepend(repliesFooter);
return isFooter; return isFooter;
}; };

85
src/components/chat/replies.ts

@ -4,6 +4,7 @@ import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager"; import appPeersManager from "../../lib/appManagers/appPeersManager";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import { ripple } from "../ripple"; import { ripple } from "../ripple";
import AvatarElement from "../avatar";
const TAG_NAME = 'replies-element'; const TAG_NAME = 'replies-element';
@ -18,6 +19,7 @@ rootScope.on('replies_updated', (e) => {
export default class RepliesElement extends HTMLElement { export default class RepliesElement extends HTMLElement {
public message: Message.message; public message: Message.message;
public type: 'footer' | 'beside'; public type: 'footer' | 'beside';
public loadPromises: Promise<any>[];
private updated = false; private updated = false;
@ -25,7 +27,7 @@ export default class RepliesElement extends HTMLElement {
super(); super();
} }
connectedCallback() { public init() {
this.render(); this.render();
this.dataset.postKey = this.message.peerId + '_' + this.message.mid; this.dataset.postKey = this.message.peerId + '_' + this.message.mid;
this.classList.add('replies', 'replies-' + this.type); this.classList.add('replies', 'replies-' + this.type);
@ -34,17 +36,62 @@ export default class RepliesElement extends HTMLElement {
public render() { public render() {
const replies = this.message.replies; const replies = this.message.replies;
/* if(this.firstChild) {
this.innerHTML = '';
} */
if(this.type === 'footer') { if(this.type === 'footer') {
let leftHTML = ''; let leftPart: HTMLElement;
if(this.firstElementChild) {
leftPart = this.firstElementChild as HTMLElement;
}
if(replies?.recent_repliers) { if(replies?.recent_repliers) {
leftHTML += '<div class="replies-footer-avatars">' if(leftPart && !leftPart.classList.contains('replies-footer-avatars')) {
let l: string[] = []; leftPart.remove();
replies.recent_repliers/* .slice().reverse() */.forEach((peer) => { leftPart = null;
l.push(`<avatar-element class="avatar-34" dialog="0" peer="${appPeersManager.getPeerId(peer)}"></avatar-element>`); }
if(!leftPart) {
leftPart = document.createElement('div');
leftPart.classList.add('replies-footer-avatars');
}
replies.recent_repliers.slice().reverse().forEach((peer, idx) => {
let avatarElem = leftPart.children[idx] as AvatarElement;
if(!avatarElem) {
avatarElem = new AvatarElement();
avatarElem.setAttribute('dialog', '0');
avatarElem.classList.add('avatar-34');
if(this.loadPromises) {
avatarElem.loadPromises = this.loadPromises;
}
}
avatarElem.setAttribute('peer', '' + appPeersManager.getPeerId(peer));
if(!avatarElem.parentNode) {
leftPart.append(avatarElem);
}
}); });
leftHTML += l.reverse().join('') + '</div>';
// if were 3 and became 2
(Array.from(leftPart.children) as HTMLElement[]).slice(replies.recent_repliers.length).forEach(el => el.remove());
} else { } else {
leftHTML = '<span class="tgico-comments"></span>'; if(leftPart && !leftPart.classList.contains('tgico-comments')) {
leftPart.remove();
leftPart = null;
}
if(!leftPart) {
leftPart = document.createElement('span');
leftPart.classList.add('tgico-comments');
}
}
if(!leftPart.parentElement) {
this.append(leftPart);
} }
let text: string; let text: string;
@ -63,11 +110,21 @@ export default class RepliesElement extends HTMLElement {
this.classList.toggle('is-unread', replies.read_max_id < replies.max_id && (!historyStorage.readMaxId || historyStorage.readMaxId < replies.max_id)); this.classList.toggle('is-unread', replies.read_max_id < replies.max_id && (!historyStorage.readMaxId || historyStorage.readMaxId < replies.max_id));
} }
this.innerHTML = `${leftHTML}<span class="replies-footer-text">${text}</span><span class="tgico-next"></span>`; let textSpan = this.children[1] as HTMLElement;
if(!textSpan) {
textSpan = document.createElement('span');
textSpan.classList.add('replies-footer-text');
const iconSpan = document.createElement('span');
iconSpan.classList.add('tgico-next');
const rippleContainer = document.createElement('div'); const rippleContainer = document.createElement('div');
this.append(rippleContainer); ripple(rippleContainer);
ripple(rippleContainer);
this.append(textSpan, iconSpan, rippleContainer);
}
textSpan.innerHTML = text;
} else { } else {
this.classList.add('bubble-beside-button'); this.classList.add('bubble-beside-button');
this.innerHTML = `<span class="tgico-commentssticker"></span><span class="replies-beside-text">${replies?.replies ? formatNumber(replies.replies, 0) : ''}</span>`; this.innerHTML = `<span class="tgico-commentssticker"></span><span class="replies-beside-text">${replies?.replies ? formatNumber(replies.replies, 0) : ''}</span>`;
@ -78,6 +135,10 @@ export default class RepliesElement extends HTMLElement {
appMessagesManager.updateMessage(this.message.peerId, this.message.mid, 'replies_updated'); appMessagesManager.updateMessage(this.message.peerId, this.message.mid, 'replies_updated');
this.updated = true; this.updated = true;
} }
if(this.loadPromises) {
this.loadPromises = undefined;
}
} }
} }

1
src/scss/partials/_avatar.scss

@ -66,7 +66,6 @@ avatar-element {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: inherit; border-radius: inherit;
user-select: none;
&.fade-in { &.fade-in {
animation: fade-in-opacity .2s ease forwards; animation: fade-in-opacity .2s ease forwards;

Loading…
Cancel
Save