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

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;
}
});
}
}