Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
14 KiB
388 lines
14 KiB
![]()
4 years ago
|
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||
|
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||
|
import { ScreenSize } from "../../helpers/mediaSizes";
|
||
|
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||
|
import PopupPinMessage from "../popupUnpinMessage";
|
||
|
import PinnedContainer from "./pinnedContainer";
|
||
|
import PinnedMessageBorder from "./pinnedMessageBorder";
|
||
|
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
|
||
|
import rootScope from "../../lib/rootScope";
|
||
|
import { findUpClassName } from "../../helpers/dom";
|
||
|
|
||
|
class AnimatedSuper {
|
||
|
static DURATION = 200;
|
||
|
static BASE_CLASS = 'animated-super';
|
||
|
container: HTMLDivElement;
|
||
|
rows: {[index: string]: {element: HTMLElement, timeout?: number, new?: true}} = {};
|
||
|
clearTimeout: number;
|
||
|
|
||
|
constructor() {
|
||
|
this.container = document.createElement('div');
|
||
|
this.container.className = AnimatedSuper.BASE_CLASS;
|
||
|
}
|
||
|
|
||
|
public getRow(index: number, animateFirst = false) {
|
||
|
if(this.rows[index]) return this.rows[index].element;
|
||
|
const row = document.createElement('div');
|
||
|
const isFirst = !Object.keys(this.rows).length && !animateFirst;
|
||
|
row.className = AnimatedSuper.BASE_CLASS + '-row' + (isFirst ? '' : ' is-hiding hide');
|
||
|
this.rows[index] = {element: row, new: true};
|
||
|
this.container.append(row);
|
||
|
return row;
|
||
|
}
|
||
|
|
||
|
public clearRow(index: number) {
|
||
|
if(!this.rows[index]) return;
|
||
|
this.rows[index].element.remove();
|
||
|
delete this.rows[index];
|
||
|
}
|
||
|
|
||
|
public clearRows(currentIndex?: number) {
|
||
|
if(this.clearTimeout) clearTimeout(this.clearTimeout);
|
||
|
this.clearTimeout = window.setTimeout(() => {
|
||
|
for(const i in this.rows) {
|
||
|
if(+i === currentIndex) continue;
|
||
|
this.clearRow(+i);
|
||
|
}
|
||
|
}, AnimatedSuper.DURATION);
|
||
|
}
|
||
|
|
||
|
public /* async */ animate(index: number, previousIndex: number, fromTop = index > previousIndex, ignorePrevious = false) {
|
||
|
if(index == previousIndex) {
|
||
|
this.clearRows(index);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//const fromTop = index > previousIndex;
|
||
|
|
||
|
const row = this.rows[index];
|
||
|
const previousRow = this.rows[previousIndex];
|
||
|
//const height = this.container.getBoundingClientRect().height;
|
||
|
|
||
|
if(!previousRow && !ignorePrevious) {
|
||
|
if(row.new) {
|
||
|
row.element.classList.remove('hide');
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const sides = ['from-top', 'from-bottom'];
|
||
|
if(!fromTop) sides.reverse();
|
||
|
|
||
|
row.element.classList.add(sides[0]);
|
||
|
row.element.classList.remove(sides[1]);
|
||
|
if(previousRow) {
|
||
|
previousRow.element.classList.add(sides[1]);
|
||
|
previousRow.element.classList.remove(sides[0]);
|
||
|
}
|
||
|
|
||
|
if(row.new) {
|
||
|
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||
|
row.element.classList.remove('hide');
|
||
|
void row.element.offsetLeft; // reflow
|
||
|
delete row.new;
|
||
|
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||
|
}
|
||
|
|
||
|
row.element.classList.toggle('is-hiding', false);
|
||
|
previousRow && previousRow.element.classList.toggle('is-hiding', true);
|
||
|
//SetTransition(row.element, 'is-hiding', false, AnimatedSuper.DURATION);
|
||
|
//previousRow && SetTransition(previousRow.element, 'is-hiding', true, AnimatedSuper.DURATION);
|
||
|
|
||
|
this.clearRows(index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class AnimatedCounter {
|
||
|
static BASE_CLASS = 'animated-counter';
|
||
|
container: HTMLElement;
|
||
|
decimals: {
|
||
|
container: HTMLElement,
|
||
|
placeholder: HTMLElement,
|
||
|
animatedSuper: AnimatedSuper
|
||
|
}[] = [];
|
||
|
previousNumber = 0;
|
||
|
clearTimeout: number;
|
||
|
|
||
|
constructor(private reverse = false) {
|
||
|
this.container = document.createElement('div');
|
||
|
this.container.className = AnimatedCounter.BASE_CLASS;
|
||
|
}
|
||
|
|
||
|
getDecimal(index: number) {
|
||
|
if(this.decimals[index]) return this.decimals[index];
|
||
|
const item = document.createElement('div');
|
||
|
item.className = AnimatedCounter.BASE_CLASS + '-decimal';
|
||
|
|
||
|
const placeholder = document.createElement('div');
|
||
|
placeholder.className = AnimatedCounter.BASE_CLASS + '-decimal-placeholder';
|
||
|
|
||
|
const animatedSuper = new AnimatedSuper();
|
||
|
animatedSuper.container.className = AnimatedCounter.BASE_CLASS + '-decimal-wrapper';
|
||
|
|
||
|
item.append(placeholder, animatedSuper.container);
|
||
|
|
||
|
this.container.append(item);
|
||
|
|
||
|
return this.decimals[index] = {container: item, placeholder, animatedSuper};
|
||
|
}
|
||
|
|
||
|
clear(number: number) {
|
||
|
if(this.clearTimeout) clearTimeout(this.clearTimeout);
|
||
|
|
||
|
const decimals = ('' + number).length;
|
||
|
if(decimals >= this.decimals.length) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.clearTimeout = window.setTimeout(() => {
|
||
|
const byDecimal = this.decimals.splice(decimals, this.decimals.length - decimals);
|
||
|
byDecimal.forEach((decimal) => {
|
||
|
decimal.container.remove();
|
||
|
});
|
||
|
}, AnimatedSuper.DURATION);
|
||
|
}
|
||
|
|
||
|
/* prepareNumber(number: number) {
|
||
|
const decimals = ('' + number).length;
|
||
|
if(this.decimals.length < decimals) {
|
||
|
for(let i = this.decimals.length; i < decimals; ++i) {
|
||
|
this.getDecimal(i);
|
||
|
}
|
||
|
}
|
||
|
} */
|
||
|
|
||
|
hideLeft(number: number) {
|
||
|
const decimals = ('' + number).length;
|
||
|
const byDecimal = this.decimals.slice(decimals);//this.decimals.splice(deleteCount, this.decimals.length - deleteCount);
|
||
|
const EMPTY_INDEX = 0;
|
||
|
byDecimal.forEach((decimal) => {
|
||
|
const row = decimal.animatedSuper.getRow(EMPTY_INDEX, true);
|
||
|
decimal.animatedSuper.animate(EMPTY_INDEX, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
|
||
|
//decimal.container.remove();
|
||
|
//decimal.animatedSuper.clearRows();
|
||
|
});
|
||
|
|
||
|
this.clear(number);
|
||
|
}
|
||
|
|
||
|
setCount(number: number) {
|
||
|
//this.prepareNumber(number);
|
||
|
|
||
|
const byDecimal = Array.from('' + number).map(n => +n);
|
||
|
byDecimal.forEach((decimalNumber, idx) => {
|
||
|
const decimal = this.getDecimal(idx);
|
||
|
const row = decimal.animatedSuper.getRow(number, true);
|
||
|
row.innerText = decimal.placeholder.innerText = '' + decimalNumber;
|
||
|
decimal.animatedSuper.animate(number, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
|
||
|
});
|
||
|
|
||
|
/* const sides = ['from-top', 'from-bottom'];
|
||
|
if(this.reverse) {
|
||
|
sides.reverse();
|
||
|
}
|
||
|
|
||
|
const isHigher = number > this.previousNumber;
|
||
|
if(!isHigher) {
|
||
|
sides.reverse();
|
||
|
}
|
||
|
|
||
|
this.container.classList.add(sides[0]);
|
||
|
this.container.classList.remove(sides[1]); */
|
||
|
|
||
|
this.hideLeft(number);
|
||
|
//this.clear(number);
|
||
|
this.previousNumber = number;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default class PinnedMessage {
|
||
|
public pinnedMessageContainer: PinnedContainer;
|
||
|
public pinnedMessageBorder: PinnedMessageBorder;
|
||
|
public pinnedIndex = 0;
|
||
|
public wasPinnedIndex = 0;
|
||
|
public locked = false;
|
||
|
public waitForScrollBottom = false;
|
||
|
|
||
|
public animatedSubtitle: AnimatedSuper;
|
||
|
public animatedMedia: AnimatedSuper;
|
||
|
public animatedCounter: AnimatedCounter;
|
||
|
|
||
|
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
|
||
|
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
|
||
|
if(appPeersManager.canPinMessage(this.appImManager.peerID)) {
|
||
|
new PopupPinMessage(this.appImManager.peerID, 0);
|
||
|
return Promise.resolve(false);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.pinnedMessageBorder = new PinnedMessageBorder();
|
||
|
this.pinnedMessageContainer.divAndCaption.border.replaceWith(this.pinnedMessageBorder.render(1, 0));
|
||
|
this.appImManager.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.appImManager.btnJoin);
|
||
|
|
||
|
this.animatedSubtitle = new AnimatedSuper();
|
||
|
this.pinnedMessageContainer.divAndCaption.subtitle.append(this.animatedSubtitle.container);
|
||
|
|
||
|
this.animatedMedia = new AnimatedSuper();
|
||
|
this.animatedMedia.container.classList.add('pinned-message-media-container');
|
||
|
this.pinnedMessageContainer.divAndCaption.content.prepend(this.animatedMedia.container);
|
||
|
|
||
|
this.animatedCounter = new AnimatedCounter(true);
|
||
|
this.pinnedMessageContainer.divAndCaption.title.innerHTML = 'Pinned Message ';
|
||
|
this.pinnedMessageContainer.divAndCaption.title.append(this.animatedCounter.container);
|
||
|
|
||
|
rootScope.on('peer_pinned_messages', (e) => {
|
||
|
const peerID = e.detail;
|
||
|
|
||
|
if(peerID == this.appImManager.peerID) {
|
||
|
this.setPinnedMessage();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public setCorrectIndex(lastScrollDirection?: number) {
|
||
|
if(this.locked) {
|
||
|
return;
|
||
|
}/* else if(this.waitForScrollBottom) {
|
||
|
if(lastScrollDirection === 1) {
|
||
|
this.waitForScrollBottom = false;
|
||
|
} else {
|
||
|
return;
|
||
|
}
|
||
|
} */
|
||
|
|
||
|
///const perf = performance.now();
|
||
|
const rect = this.appImManager.scrollable.container.getBoundingClientRect();
|
||
|
const x = Math.ceil(rect.left + ((rect.right - rect.left) / 2) + 1);
|
||
|
const y = Math.floor(rect.top + rect.height - 1);
|
||
|
let el: HTMLElement = document.elementFromPoint(x, y) as any;
|
||
|
//this.appImManager.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el, x, y);
|
||
|
el = findUpClassName(el, 'bubble');
|
||
|
|
||
|
if(el && el.dataset.mid !== undefined) {
|
||
|
const mid = +el.dataset.mid;
|
||
|
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID).then(mids => {
|
||
|
let currentIndex = mids.findIndex(_mid => _mid <= mid);
|
||
|
if(currentIndex === -1) {
|
||
|
currentIndex = mids.length ? mids.length - 1 : 0;
|
||
|
}
|
||
|
|
||
|
this.appImManager.log('pinned currentIndex', currentIndex);
|
||
|
|
||
|
const changed = this.pinnedIndex != currentIndex;
|
||
|
if(changed) {
|
||
|
if(this.waitForScrollBottom) {
|
||
|
if(lastScrollDirection === 1) { // если проскроллил вниз - разблокировать
|
||
|
this.waitForScrollBottom = false;
|
||
|
} else if(this.pinnedIndex > currentIndex) { // если не скроллил вниз и пытается поставить нижний пиннед - выйти
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.pinnedIndex = currentIndex;
|
||
|
this.setPinnedMessage();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async followPinnedMessage(mid: number) {
|
||
|
const message = this.appMessagesManager.getMessage(mid);
|
||
|
if(message && !message.deleted) {
|
||
|
this.locked = true;
|
||
|
|
||
|
try {
|
||
|
const mids = await this.appMessagesManager.getPinnedMessages(message.peerID);
|
||
|
const index = mids.indexOf(mid);
|
||
|
|
||
|
this.pinnedIndex = index >= (mids.length - 1) ? 0 : index + 1;
|
||
|
this.setPinnedMessage();
|
||
|
|
||
|
const setPeerPromise = this.appImManager.setPeer(message.peerID, mid);
|
||
|
if(setPeerPromise instanceof Promise) {
|
||
|
await setPeerPromise;
|
||
|
}
|
||
|
|
||
|
await this.appImManager.scrollable.scrollLockedPromise;
|
||
|
} catch(err) {
|
||
|
this.appImManager.log.error('[PM]: followPinnedMessage error:', err);
|
||
|
}
|
||
|
|
||
|
// подождём, пока скролл остановится
|
||
|
setTimeout(() => {
|
||
|
this.locked = false;
|
||
|
this.waitForScrollBottom = true;
|
||
|
}, 50);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public onChangeScreen(from: ScreenSize, to: ScreenSize) {
|
||
|
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile
|
||
|
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
|
||
|
}
|
||
|
|
||
|
public setPinnedMessage() {
|
||
|
/////this.log('setting pinned message', message);
|
||
|
//return;
|
||
|
const promise: Promise<any> = this.appImManager.setPeerPromise || this.appImManager.messagesQueuePromise || Promise.resolve();
|
||
|
Promise.all([
|
||
|
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID),
|
||
|
promise
|
||
|
]).then(([mids]) => {
|
||
|
//const mids = results[0];
|
||
|
if(mids.length) {
|
||
|
const pinnedIndex = this.pinnedIndex >= mids.length ? mids.length - 1 : this.pinnedIndex;
|
||
|
const message = this.appMessagesManager.getMessage(mids[pinnedIndex]);
|
||
|
|
||
|
//this.animatedCounter.prepareNumber(mids.length);
|
||
|
|
||
|
//setTimeout(() => {
|
||
|
const isLast = pinnedIndex === 0;
|
||
|
this.animatedCounter.container.classList.toggle('is-last', isLast);
|
||
|
//SetTransition(this.animatedCounter.container, 'is-last', isLast, AnimatedSuper.DURATION);
|
||
|
if(!isLast) {
|
||
|
this.animatedCounter.setCount(mids.length - pinnedIndex);
|
||
|
}
|
||
|
//}, 100);
|
||
|
|
||
|
//this.pinnedMessageContainer.fill(undefined, message.message, message);
|
||
|
this.pinnedMessageContainer.toggle(false);
|
||
|
|
||
|
const fromTop = pinnedIndex > this.wasPinnedIndex;
|
||
|
|
||
|
this.appImManager.log('[PM]: setPinnedMessage: fromTop', fromTop, pinnedIndex, this.wasPinnedIndex);
|
||
|
|
||
|
const writeTo = this.animatedSubtitle.getRow(pinnedIndex);
|
||
|
const writeMediaTo = this.animatedMedia.getRow(pinnedIndex);
|
||
|
writeMediaTo.classList.add('pinned-message-media');
|
||
|
const isMediaSet = wrapReplyDivAndCaption({
|
||
|
title: undefined,
|
||
|
titleEl: null,
|
||
|
subtitle: message.message,
|
||
|
subtitleEl: writeTo,
|
||
|
message,
|
||
|
mediaEl: writeMediaTo
|
||
|
});
|
||
|
|
||
|
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-media', isMediaSet);
|
||
|
|
||
|
if(this.wasPinnedIndex != this.pinnedIndex) {
|
||
|
this.animatedSubtitle.animate(pinnedIndex, this.wasPinnedIndex);
|
||
|
if(isMediaSet) {
|
||
|
this.animatedMedia.animate(pinnedIndex, this.wasPinnedIndex);
|
||
|
} else {
|
||
|
this.animatedMedia.clearRows();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.pinnedMessageBorder.render(mids.length, mids.length - pinnedIndex - 1);
|
||
|
this.wasPinnedIndex = pinnedIndex;
|
||
|
this.pinnedMessageContainer.divAndCaption.container.dataset.mid = '' + message.mid;
|
||
|
} else {
|
||
|
this.pinnedMessageContainer.toggle(true);
|
||
|
this.wasPinnedIndex = 0;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|