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.

652 lines
22 KiB

import { horizontalMenu, formatPhoneNumber, putPreloader, renderImageFromUrl } from "../../components/misc";
import Scrollable from '../../components/scrollable';
5 years ago
import { $rootScope } from "../utils";
5 years ago
import appMessagesManager from "./appMessagesManager";
import appPhotosManager from "./appPhotosManager";
import appPeersManager from "./appPeersManager";
import appUsersManager from "./appUsersManager";
import appProfileManager from "./appProfileManager";
import { RichTextProcessor } from "../richtextprocessor";
import { logger } from "../polyfill";
import appImManager from "./appImManager";
import appMediaViewer from "./appMediaViewer";
import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
5 years ago
5 years ago
const testScroll = false;
5 years ago
class AppSidebarRight {
public sidebarEl = document.getElementById('column-right') as HTMLDivElement;
public profileContainer = this.sidebarEl.querySelector('.profile-container') as HTMLDivElement;
public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement;
5 years ago
public profileElements = {
avatar: this.profileContentEl.querySelector('.profile-avatar') as HTMLDivElement,
name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement,
subtitle: this.profileContentEl.querySelector('.profile-subtitle') as HTMLDivElement,
bio: this.profileContentEl.querySelector('.profile-row-bio') as HTMLDivElement,
username: this.profileContentEl.querySelector('.profile-row-username') as HTMLDivElement,
phone: this.profileContentEl.querySelector('.profile-row-phone') as HTMLDivElement,
notificationsRow: this.profileContentEl.querySelector('.profile-row-notifications') as HTMLDivElement,
5 years ago
notificationsCheckbox: this.profileContentEl.querySelector('#profile-notifications') as HTMLInputElement,
notificationsStatus: this.profileContentEl.querySelector('.profile-row-notifications > p') as HTMLParagraphElement
};
public sharedMedia: {
[type: string]: HTMLDivElement
} = {
contentMembers: this.profileContentEl.querySelector('#content-members') as HTMLDivElement,
contentMedia: this.profileContentEl.querySelector('#content-media') as HTMLDivElement,
contentDocuments: this.profileContentEl.querySelector('#content-docs') as HTMLDivElement,
contentLinks: this.profileContentEl.querySelector('#content-links') as HTMLDivElement,
contentAudio: this.profileContentEl.querySelector('#content-audio') as HTMLDivElement,
};
5 years ago
public lastSharedMediaDiv: HTMLDivElement = null;
5 years ago
private loadSidebarMediaPromises: {[type: string]: Promise<void>} = {};
private loadedAllMedia: {[type: string]: boolean} = {};
5 years ago
5 years ago
public sharedMediaTypes = [
'inputMessagesFilterContacts',
'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument',
'inputMessagesFilterUrl',
'inputMessagesFilterMusic'
5 years ago
];
public sharedMediaType: string = '';
private sharedMediaSelected: HTMLDivElement = null;
5 years ago
private lazyLoadQueueSidebar = new LazyLoadQueue(5);
5 years ago
/* public minMediaID: {
[type: string]: number
} = {}; */
public cleared: {
[type: string]: boolean
} = {};
5 years ago
5 years ago
public historiesStorage: {
[peerID: number]: {
[type: string]: number[]
}
} = {};
public usedFromHistory: {
[type: string]: number
} = {};
5 years ago
5 years ago
private log = logger('SR');
5 years ago
private peerID = 0;
5 years ago
public scroll: Scrollable = null;
private savedVirtualStates: {
[id: number]: Scrollable['state']
5 years ago
} = {};
5 years ago
private profileTabs: HTMLUListElement;
private prevTabID = -1;
5 years ago
private mediaDivsByIDs: {
[mid: number]: HTMLDivElement
} = {};
public urlsToRevoke: string[] = [];
private searchContainer = this.sidebarEl.querySelector('#search-private-container') as HTMLDivElement;
public searchCloseBtn = this.searchContainer.querySelector('.sidebar-close-button') as HTMLButtonElement;
private searchInput = document.getElementById('private-search') as HTMLInputElement;
public privateSearch = new AppSearch(this.searchContainer.querySelector('.chats-container') as HTMLDivElement, this.searchInput, {
messages: new SearchGroup('Private Search', 'messages')
});
5 years ago
5 years ago
constructor() {
let container = this.profileContentEl.querySelector('.profile-tabs-content') as HTMLDivElement;
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement;
5 years ago
this.scroll = new Scrollable(this.profileContainer, 'y', 1200, 'SR');
this.scroll.container.addEventListener('scroll', this.onSidebarScroll.bind(this));
this.scroll.onScrolledBottom = () => {
if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length && this.sharedMediaSelected.childElementCount/* && false */) {
this.log('onScrolledBottom will load media');
5 years ago
this.loadSidebarMedia(true);
}
};
horizontalMenu(this.profileTabs, container, (id, tabContent) => {
5 years ago
if(this.prevTabID == id) return;
5 years ago
this.sharedMediaType = this.sharedMediaTypes[id];
this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement;
5 years ago
if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix
this.loadSidebarMedia(true);
}
if(this.prevTabID != -1) {
this.savedVirtualStates[this.prevTabID] = this.scroll.state;
}
5 years ago
this.prevTabID = id;
5 years ago
this.log('setVirtualContainer', id, this.sharedMediaSelected);
this.scroll.setVirtualContainer(this.sharedMediaSelected);
5 years ago
if(this.savedVirtualStates[id]) {
this.log(this.savedVirtualStates[id]);
this.scroll.state = this.savedVirtualStates[id];
}
5 years ago
}, this.onSidebarScroll.bind(this));
5 years ago
5 years ago
let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement;
sidebarCloseBtn.addEventListener('click', () => {
this.toggleSidebar(false);
});
this.searchCloseBtn.addEventListener('click', () => {
this.searchContainer.classList.remove('active');
this.privateSearch.reset();
});
5 years ago
5 years ago
this.sharedMedia.contentMedia.addEventListener('click', (e) => {
let target = e.target as HTMLDivElement;
5 years ago
let messageID = +target.dataset.mid;
5 years ago
if(!messageID) {
this.log.warn('no messageID by click on target:', target);
return;
}
5 years ago
5 years ago
let message = appMessagesManager.getMessage(messageID);
5 years ago
let ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort();
let idx = ids.findIndex(i => i == messageID);
let targets = ids.map(id => ({element: this.mediaDivsByIDs[id], mid: id}));
5 years ago
appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), () => this.loadSidebarMedia(true));
5 years ago
});
5 years ago
this.profileElements.notificationsCheckbox.addEventListener('change', () => {
5 years ago
//let checked = this.profileElements.notificationsCheckbox.checked;
appImManager.mutePeer();
});
5 years ago
window.addEventListener('resize', this.onSidebarScroll.bind(this));
5 years ago
5 years ago
if(testScroll) {
let div = document.createElement('div');
for(let i = 0; i < 500; ++i) {
//div.insertAdjacentHTML('beforeend', `<div style="background-image: url(assets/img/camomile.jpg);"></div>`);
5 years ago
div.insertAdjacentHTML('beforeend', `<div data-id="${i / 3 | 0}">${i / 3 | 0}</div>`);
5 years ago
5 years ago
if((i + 1) % 3 == 0) {
this.sharedMedia.contentMedia.append(div);
div = document.createElement('div');
}
5 years ago
5 years ago
div.dataset.id = '' + (i / 3 | 0);
}
this.sharedMedia.contentMedia.append(div);
(this.profileTabs.children[1] as HTMLLIElement).click(); // set media
}
5 years ago
}
public beginSearch() {
this.toggleSidebar(true);
this.searchContainer.classList.add('active');
this.privateSearch.beginSearch(this.peerID);
}
5 years ago
5 years ago
public onSidebarScroll() {
this.lazyLoadQueueSidebar.check();
}
5 years ago
5 years ago
public toggleSidebar(enable?: boolean) {
5 years ago
/////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl));
5 years ago
5 years ago
if(enable !== undefined) {
if(enable) {
setTimeout(() => this.lazyLoadQueueSidebar.check(), 200);
this.sidebarEl.classList.add('active');
} else this.sidebarEl.classList.remove('active');
5 years ago
return;
}
this.sidebarEl.classList.toggle('active');
5 years ago
}
5 years ago
public performSearchResult(ids: number[], type: string) {
let peerID = this.peerID;
let sharedMediaDiv: HTMLDivElement;
let messages: any[] = [];
for(let mid of ids) {
let message = appMessagesManager.getMessage(mid);
if(message.media) messages.push(message);
}
let elemsToAppend: HTMLElement[] = [];
// https://core.telegram.org/type/MessagesFilter
switch(type) {
case 'inputMessagesFilterPhotoVideo': {
sharedMediaDiv = this.sharedMedia.contentMedia;
for(let message of messages) {
let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document);
if(!media) {
//this.log('no media!', message);
continue;
}
if(media._ == 'document' && media.type != 'video'/* && media.type != 'gif' */) {
//this.log('broken video', media);
continue;
}
let div = document.createElement('div');
//console.log(message, photo);
let isPhoto = media._ == 'photo';
let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null;
if(!photo || !photo.downloaded) {
//this.log('inputMessagesFilterPhotoVideo', message, media, photo, div);
let sizes = media.sizes || media.thumbs;
if(sizes && sizes[0].bytes) {
appPhotosManager.setAttachmentPreview(sizes[0].bytes, div, false, true);
} /* else {
this.log('no stripped size', message, media);
} */
}
//this.log('inputMessagesFilterPhotoVideo', message, media);
let load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200))
.then((blob) => {
if($rootScope.selectedPeerID != peerID) {
this.log.warn('peer changed');
return;
}
if(photo && photo.url) {
renderImageFromUrl(div, photo.url);
} else {
let url = URL.createObjectURL(blob);
this.urlsToRevoke.push(url);
let img = new Image();
img.src = url;
img.onload = () => {
div.style.backgroundImage = 'url(' + url + ')';
};
}
//div.style.backgroundImage = 'url(' + url + ')';
});
div.dataset.mid = '' + message.mid;
if(photo && photo.downloaded) load();
else this.lazyLoadQueueSidebar.push({div, load});
this.lastSharedMediaDiv.append(div);
if(this.lastSharedMediaDiv.childElementCount == 3) {
if(!this.scroll.contains(this.lastSharedMediaDiv)) {
elemsToAppend.push(this.lastSharedMediaDiv);
}
this.lastSharedMediaDiv = document.createElement('div');
}
this.mediaDivsByIDs[message.mid] = div;
//sharedMediaDiv.append(div);
}
break;
}
case 'inputMessagesFilterDocument': {
sharedMediaDiv = this.sharedMedia.contentDocuments;
for(let message of messages) {
if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') {
continue;
}
let doc = message.media.document;
if(doc.attributes) {
if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) {
continue;
}
}
//this.log('come back down to my knees', message);
let div = wrapDocument(message.media.document, true);
elemsToAppend.push(div);
}
break;
}
case 'inputMessagesFilterUrl': {
sharedMediaDiv = this.sharedMedia.contentLinks;
for(let message of messages) {
if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') {
continue;
}
let webpage = message.media.webpage;
let div = document.createElement('div');
let previewDiv = document.createElement('div');
previewDiv.classList.add('preview');
//this.log('wrapping webpage', webpage);
if(webpage.photo) {
let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 60, 60))
.then(() => {
if($rootScope.selectedPeerID != peerID) {
this.log.warn('peer changed');
return;
}
renderImageFromUrl(previewDiv, webpage.photo.url);
});
this.lazyLoadQueueSidebar.push({div: previewDiv, load});
} else {
previewDiv.innerText = (webpage.title || webpage.description || webpage.url || webpage.display_url).slice(0, 1);
previewDiv.classList.add('empty');
}
let title = webpage.rTitle || '';
let subtitle = webpage.rDescription || '';
let url = RichTextProcessor.wrapRichText(webpage.url || '');
if(!title) {
//title = new URL(webpage.url).hostname;
title = webpage.display_url.split('/', 1)[0];
}
div.append(previewDiv);
div.insertAdjacentHTML('beforeend', `
<div class="title">${title}</div>
<div class="subtitle">${subtitle}</div>
<div class="url">${url}</div>
`);
if(div.innerText.trim().length) {
elemsToAppend.push(div);
}
}
break;
}
case 'inputMessagesFilterMusic': {
sharedMediaDiv = this.sharedMedia.contentAudio;
for(let message of messages) {
if(!message.media.document || message.media.document.type != 'audio') {
continue;
}
let div = wrapDocument(message.media.document, true);
elemsToAppend.push(div);
}
break;
}
default:
//console.warn('death is my friend', message);
break;
}
if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) {
elemsToAppend.push(this.lastSharedMediaDiv);
}
if(elemsToAppend.length) {
//window.requestAnimationFrame(() => {
elemsToAppend.forEach(el => this.scroll.append(el));
//});
}
if(sharedMediaDiv) {
let parent = sharedMediaDiv.parentElement;
if(parent.lastElementChild.classList.contains('preloader')) {
parent.lastElementChild.remove();
}
}
this.onSidebarScroll();
}
5 years ago
public loadSidebarMedia(single = false) {
if(testScroll/* || 1 == 1 */) {
5 years ago
return;
}
this.log('loadSidebarMedia', single, this.peerID);
5 years ago
let peerID = this.peerID;
5 years ago
let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes;
typesToLoad = typesToLoad.filter(type => !this.loadedAllMedia[type]);
if(!typesToLoad.length) return;
5 years ago
let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {});
this.scroll.lock();
5 years ago
5 years ago
let promises = typesToLoad.map(type => {
if(this.loadSidebarMediaPromises[type]) return this.loadSidebarMediaPromises[type];
5 years ago
let history = historyStorage[type] ?? (historyStorage[type] = []);
let loadCount = (appPhotosManager.windowH / 130 | 0) * 3;
// render from cache
if(history.length && this.usedFromHistory[type] < history.length && this.cleared[type]) {
let ids = history.slice(this.usedFromHistory[type], this.usedFromHistory[type] + loadCount);
this.log('will render from cache', this.usedFromHistory[type], history, ids, loadCount);
this.usedFromHistory[type] += ids.length;
this.performSearchResult(ids, type);
return;
}
5 years ago
5 years ago
// заливать новую картинку сюда только после полной отправки!
//let maxID = this.minMediaID[type] || 0;
let maxID = history[history.length - 1] || 0;
5 years ago
5 years ago
let ids = !maxID && appMessagesManager.historiesStorage[peerID]
5 years ago
? appMessagesManager.historiesStorage[peerID].history.slice() : [];
5 years ago
maxID = !maxID && ids.length ? ids[ids.length - 1] : maxID;
this.log('search house of glass pre', type, ids, maxID);
5 years ago
//let loadCount = history.length ? 50 : 15;
return this.loadSidebarMediaPromises[type] = appMessagesManager.getSearch(peerID, '', {_: type}, maxID, loadCount)
5 years ago
.then(value => {
ids = ids.concat(value.history);
history.push(...ids);
5 years ago
this.log('search house of glass', type, value, ids, this.cleared);
5 years ago
if($rootScope.selectedPeerID != peerID) {
this.log.warn('peer changed');
return;
}
5 years ago
if(value.history.length < loadCount) {
this.loadedAllMedia[type] = true;
5 years ago
}
if(this.cleared[type]) {
//ids = history;
delete this.cleared[type];
}
if(ids.length) {
this.performSearchResult(ids, type);
}
5 years ago
}, (err) => {
this.log.error('load error:', err);
}).then(() => {
5 years ago
this.loadSidebarMediaPromises[type] = null;
});
});
5 years ago
return Promise.all(promises).then(() => {
this.scroll.unlock();
});
5 years ago
}
5 years ago
5 years ago
public fillProfileElements() {
let peerID = this.peerID = $rootScope.selectedPeerID;
this.loadSidebarMediaPromises = {};
this.loadedAllMedia = {};
this.lastSharedMediaDiv = document.createElement('div');
5 years ago
//this.log('fillProfileElements');
5 years ago
window.requestAnimationFrame(() => {
this.profileContentEl.parentElement.scrollTop = 0;
this.profileElements.bio.style.display = 'none';
this.profileElements.phone.style.display = 'none';
this.profileElements.username.style.display = 'none';
this.profileElements.notificationsRow.style.display = '';
this.profileElements.notificationsCheckbox.checked = true;
this.profileElements.notificationsStatus.innerText = 'Enabled';
5 years ago
Object.keys(this.sharedMedia).forEach(key => {
this.sharedMedia[key].innerHTML = '';
let parent = this.sharedMedia[key].parentElement;
if(!parent.querySelector('.preloader')) {
putPreloader(parent, true);
}
});
5 years ago
this.savedVirtualStates = {};
this.prevTabID = -1;
this.scroll.setVirtualContainer(null);
(this.profileTabs.firstElementChild.children[1] as HTMLLIElement).click(); // set media
5 years ago
//this.scroll.getScrollTopOffset();
5 years ago
this.loadSidebarMedia(true);
});
5 years ago
this.mediaDivsByIDs = {};
5 years ago
this.lazyLoadQueueSidebar.clear();
5 years ago
this.urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
});
this.urlsToRevoke.length = 0;
5 years ago
5 years ago
this.sharedMediaTypes.forEach(type => {
//this.minMediaID[type] = 0;
this.cleared[type] = true;
this.usedFromHistory[type] = 0;
5 years ago
});
5 years ago
5 years ago
let setText = (text: string, el: HTMLDivElement) => {
5 years ago
window.requestAnimationFrame(() => {
if(el.childElementCount > 1) {
el.firstElementChild.remove();
}
5 years ago
let p = document.createElement('p');
p.innerHTML = text;
el.prepend(p);
5 years ago
el.style.display = '';
//this.scroll.getScrollTopOffset();
5 years ago
});
5 years ago
};
5 years ago
5 years ago
// username
if(peerID != appImManager.myID) {
let username = appPeersManager.getPeerUsername(peerID);
if(username) {
setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username);
}
5 years ago
5 years ago
let dialog: any = appMessagesManager.getDialogByPeerID(peerID);
if(dialog.length) {
dialog = dialog[0];
let muted = false;
if(dialog.notify_settings && dialog.notify_settings.mute_until) {
muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date();
}
5 years ago
5 years ago
appImManager.setMutedState(muted);
}
} else {
window.requestAnimationFrame(() => {
this.profileElements.notificationsRow.style.display = 'none';
//this.scroll.getScrollTopOffset();
})
5 years ago
}
5 years ago
if(peerID > 0) {
let user = appUsersManager.getUser(peerID);
if(user.phone && peerID != appImManager.myID) {
setText('+' + formatPhoneNumber(user.phone).formatted, this.profileElements.phone);
5 years ago
}
5 years ago
appProfileManager.getProfile(peerID, true).then(userFull => {
if(this.peerID != peerID) {
5 years ago
this.log.warn('peer changed');
return;
}
5 years ago
if(userFull.rAbout && peerID != appImManager.myID) {
5 years ago
setText(userFull.rAbout, this.profileElements.bio);
}
5 years ago
//this.log('userFull', userFull);
5 years ago
5 years ago
if(userFull.pinned_msg_id) { // request pinned message
appImManager.pinnedMsgID = userFull.pinned_msg_id;
appMessagesManager.wrapSingleMessage(userFull.pinned_msg_id);
}
5 years ago
//this.scroll.getScrollTopOffset();
5 years ago
});
} else {
let chat = appPeersManager.getPeer(peerID);
5 years ago
5 years ago
appProfileManager.getChatFull(chat.id).then((chatFull: any) => {
if(this.peerID != peerID) {
5 years ago
this.log.warn('peer changed');
return;
}
5 years ago
//this.log('chatInfo res 2:', chatFull);
5 years ago
5 years ago
if(chatFull.about) {
setText(RichTextProcessor.wrapRichText(chatFull.about), this.profileElements.bio);
}
5 years ago
//this.scroll.getScrollTopOffset();
5 years ago
});
5 years ago
}
5 years ago
//this.scroll.getScrollTopOffset();
5 years ago
//this.loadSidebarMedia();
}
}
export default new AppSidebarRight();