Create group & channel; poll prepare
This commit is contained in:
parent
1c63bf30c1
commit
a884a9cea8
@ -1,161 +1,7 @@
|
||||
import appSidebarRight from "../lib/appManagers/appSidebarRight";
|
||||
import Scrollable from "./scrollable_new";
|
||||
import appProfileManager from "../lib/appManagers/appProfileManager";
|
||||
import { appPeersManager } from "../lib/services";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import appDialogsManager from "../lib/appManagers/appDialogsManager";
|
||||
import appChatsManager from "../lib/appManagers/appChatsManager";
|
||||
import appUsersManager from "../lib/appManagers/appUsersManager";
|
||||
import { $rootScope, findUpTag, findUpClassName, cancelEvent } from "../lib/utils";
|
||||
import { putPreloader } from "./misc";
|
||||
|
||||
class AppSelectPeers {
|
||||
public container = document.createElement('div');
|
||||
private chatList = document.createElement('ul');
|
||||
private chatsContainer = document.createElement('div');
|
||||
private scrollable: Scrollable;
|
||||
private selectedScrollable: Scrollable;
|
||||
|
||||
private selectedContainer = document.createElement('div');
|
||||
private searchInput = document.createElement('input');
|
||||
|
||||
private selected: {[peerID: number]: HTMLDivElement} = {};
|
||||
|
||||
public freezed = false;
|
||||
|
||||
constructor(private appendTo: HTMLDivElement, private onChange: (length: number) => void) {
|
||||
this.container.classList.add('selector');
|
||||
|
||||
let topContainer = document.createElement('div');
|
||||
topContainer.classList.add('selector-search-container');
|
||||
|
||||
this.selectedContainer.classList.add('selector-search');
|
||||
this.searchInput.placeholder = 'Select chat';
|
||||
this.searchInput.type = 'text';
|
||||
this.selectedContainer.append(this.searchInput);
|
||||
topContainer.append(this.selectedContainer);
|
||||
this.selectedScrollable = new Scrollable(topContainer);
|
||||
|
||||
let delimiter = document.createElement('hr');
|
||||
|
||||
this.chatsContainer.classList.add('chats-container');
|
||||
this.chatsContainer.append(this.chatList);
|
||||
this.scrollable = new Scrollable(this.chatsContainer);
|
||||
|
||||
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
|
||||
let offsetIndex = 0;
|
||||
appMessagesManager.getConversations(offsetIndex, 50, 0).then(value => {
|
||||
let dialogs = value.dialogs;
|
||||
let myID = $rootScope.myID;
|
||||
|
||||
offsetIndex = dialogs[value.dialogs.length - 1].index || 0;
|
||||
|
||||
if(dialogs[0].peerID != myID) {
|
||||
dialogs.findAndSplice(d => d.peerID == myID);
|
||||
dialogs.unshift({
|
||||
peerID: myID,
|
||||
pFlags: {}
|
||||
} as any);
|
||||
}
|
||||
|
||||
dialogs.forEach(dialog => {
|
||||
let peerID = dialog.peerID;
|
||||
|
||||
let {dom} = appDialogsManager.addDialog(dialog, this.chatList, false, false);
|
||||
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
|
||||
|
||||
let subtitle = '';
|
||||
if(peerID < 0) {
|
||||
subtitle = appChatsManager.getChatMembersString(-peerID);
|
||||
} else if(peerID == myID) {
|
||||
subtitle = 'chat with yourself';
|
||||
} else {
|
||||
subtitle = appUsersManager.getUserStatusString(peerID);
|
||||
if(subtitle == 'online') {
|
||||
subtitle = `<i>${subtitle}</i>`;
|
||||
}
|
||||
}
|
||||
|
||||
dom.lastMessageSpan.innerHTML = subtitle;
|
||||
});
|
||||
});
|
||||
|
||||
this.chatList.addEventListener('click', (e) => {
|
||||
let target = e.target as HTMLElement;
|
||||
cancelEvent(e);
|
||||
|
||||
if(this.freezed) return;
|
||||
|
||||
if(target.tagName != 'LI') {
|
||||
target = findUpTag(target, 'LI');
|
||||
}
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let peerID = +target.getAttribute('data-peerID');
|
||||
target.classList.toggle('active');
|
||||
if(peerID in this.selected) {
|
||||
this.remove(peerID);
|
||||
} else {
|
||||
this.add(peerID);
|
||||
}
|
||||
|
||||
let checkbox = target.querySelector('input') as HTMLInputElement;
|
||||
checkbox.checked = !checkbox.checked;
|
||||
});
|
||||
|
||||
this.selectedContainer.addEventListener('click', (e) => {
|
||||
if(this.freezed) return;
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpClassName(target, 'selector-user');
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let peerID = target.dataset.peerID;
|
||||
let li = this.chatList.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
|
||||
li.click();
|
||||
});
|
||||
|
||||
this.container.append(topContainer, delimiter, this.chatsContainer);
|
||||
appendTo.append(this.container);
|
||||
}
|
||||
|
||||
private add(peerID: number) {
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('selector-user', 'scale-in');
|
||||
div.dataset.peerID = '' + peerID;
|
||||
this.selected[peerID] = div;
|
||||
|
||||
let title = appPeersManager.getPeerTitle(peerID, false, true);
|
||||
|
||||
let avatarDiv = document.createElement('div');
|
||||
avatarDiv.classList.add('user-avatar', 'tgico');
|
||||
appProfileManager.putPhoto(avatarDiv, peerID);
|
||||
|
||||
div.innerHTML = title;
|
||||
div.insertAdjacentElement('afterbegin', avatarDiv);
|
||||
|
||||
this.selectedContainer.insertBefore(div, this.searchInput);
|
||||
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
|
||||
this.onChange(Object.keys(this.selected).length);
|
||||
}
|
||||
|
||||
private remove(peerID: number) {
|
||||
let div = this.selected[peerID];
|
||||
div.classList.remove('scale-in');
|
||||
void div.offsetWidth;
|
||||
div.classList.add('scale-out');
|
||||
div.addEventListener('animationend', () => {
|
||||
delete this.selected[peerID];
|
||||
div.remove();
|
||||
this.onChange(Object.keys(this.selected).length);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
public getSelected() {
|
||||
return Object.keys(this.selected).map(p => +p);
|
||||
}
|
||||
}
|
||||
import { AppSelectPeers } from "./appSelectPeers";
|
||||
|
||||
class AppForward {
|
||||
private container = document.getElementById('forward-container') as HTMLDivElement;
|
||||
|
@ -13,10 +13,10 @@ export class SearchGroup {
|
||||
nameEl: HTMLDivElement;
|
||||
list: HTMLUListElement;
|
||||
|
||||
constructor(public name: string, public type: string, private clearable = true, className?: string) {
|
||||
constructor(public name: string, public type: string, private clearable = true, className?: string, clickable = true) {
|
||||
this.list = document.createElement('ul');
|
||||
this.container = document.createElement('div');
|
||||
if(className) this.container.classList.add(className);
|
||||
if(className) this.container.className = className;
|
||||
this.nameEl = document.createElement('div');
|
||||
this.nameEl.classList.add('search-group__name');
|
||||
this.nameEl.innerText = name;
|
||||
@ -25,7 +25,9 @@ export class SearchGroup {
|
||||
this.container.append(this.nameEl, this.list);
|
||||
this.container.style.display = 'none';
|
||||
|
||||
appDialogsManager.setListClickListener(this.list);
|
||||
if(clickable) {
|
||||
appDialogsManager.setListClickListener(this.list);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
214
src/components/appSelectPeers.ts
Normal file
214
src/components/appSelectPeers.ts
Normal file
@ -0,0 +1,214 @@
|
||||
import Scrollable from "./scrollable_new";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import { $rootScope, cancelEvent, findUpTag, findUpClassName } from "../lib/utils";
|
||||
import appDialogsManager from "../lib/appManagers/appDialogsManager";
|
||||
import appChatsManager from "../lib/appManagers/appChatsManager";
|
||||
import appUsersManager from "../lib/appManagers/appUsersManager";
|
||||
import { appPeersManager } from "../lib/services";
|
||||
import appProfileManager from "../lib/appManagers/appProfileManager";
|
||||
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
||||
|
||||
export class AppSelectPeers {
|
||||
public container = document.createElement('div');
|
||||
private list = document.createElement('ul');
|
||||
private chatsContainer = document.createElement('div');
|
||||
private scrollable: Scrollable;
|
||||
private selectedScrollable: Scrollable;
|
||||
|
||||
private selectedContainer = document.createElement('div');
|
||||
private input = document.createElement('input');
|
||||
|
||||
private selected: {[peerID: number]: HTMLDivElement} = {};
|
||||
|
||||
public freezed = false;
|
||||
|
||||
private myID = $rootScope.myID;
|
||||
|
||||
private offsetIndex = 0;
|
||||
private promise: Promise<number[]>;
|
||||
|
||||
private query = '';
|
||||
private cachedContacts: number[];
|
||||
|
||||
constructor(private appendTo: HTMLDivElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs') {
|
||||
this.container.classList.add('selector');
|
||||
|
||||
let topContainer = document.createElement('div');
|
||||
topContainer.classList.add('selector-search-container');
|
||||
|
||||
this.selectedContainer.classList.add('selector-search');
|
||||
this.input.placeholder = peerType == 'contacts' ? 'Add People...' : 'Select chat';
|
||||
this.input.type = 'text';
|
||||
this.selectedContainer.append(this.input);
|
||||
topContainer.append(this.selectedContainer);
|
||||
this.selectedScrollable = new Scrollable(topContainer);
|
||||
|
||||
let delimiter = document.createElement('hr');
|
||||
|
||||
this.chatsContainer.classList.add('chats-container');
|
||||
this.chatsContainer.append(this.list);
|
||||
this.scrollable = new Scrollable(this.chatsContainer);
|
||||
|
||||
this.list.addEventListener('click', (e) => {
|
||||
let target = e.target as HTMLElement;
|
||||
cancelEvent(e);
|
||||
|
||||
if(this.freezed) return;
|
||||
|
||||
if(target.tagName != 'LI') {
|
||||
target = findUpTag(target, 'LI');
|
||||
}
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let peerID = +target.getAttribute('data-peerID');
|
||||
target.classList.toggle('active');
|
||||
if(peerID in this.selected) {
|
||||
this.remove(peerID);
|
||||
} else {
|
||||
this.add(peerID);
|
||||
}
|
||||
|
||||
let checkbox = target.querySelector('input') as HTMLInputElement;
|
||||
checkbox.checked = !checkbox.checked;
|
||||
});
|
||||
|
||||
this.selectedContainer.addEventListener('click', (e) => {
|
||||
if(this.freezed) return;
|
||||
let target = e.target as HTMLElement;
|
||||
target = findUpClassName(target, 'selector-user');
|
||||
|
||||
if(!target) return;
|
||||
|
||||
let peerID = target.dataset.peerID;
|
||||
let li = this.list.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
|
||||
li.click();
|
||||
});
|
||||
|
||||
this.input.addEventListener('input', () => {
|
||||
let value = this.input.value;
|
||||
if(this.query != value) {
|
||||
if(this.peerType == 'contacts') {
|
||||
this.cachedContacts = null;
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
this.list.innerHTML = '';
|
||||
this.query = value;
|
||||
|
||||
console.log('selectPeers input:', this.query);
|
||||
this.getMoreResults();
|
||||
}
|
||||
});
|
||||
|
||||
this.scrollable.onScrolledBottom = () => {
|
||||
this.getMoreResults();
|
||||
};
|
||||
|
||||
this.container.append(topContainer, delimiter, this.chatsContainer);
|
||||
appendTo.append(this.container);
|
||||
|
||||
this.getMoreResults();
|
||||
}
|
||||
|
||||
private getMoreDialogs() {
|
||||
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
|
||||
appMessagesManager.getConversations(this.offsetIndex, 50, 0).then(value => {
|
||||
let dialogs = value.dialogs;
|
||||
|
||||
this.offsetIndex = dialogs[value.dialogs.length - 1].index || 0;
|
||||
|
||||
if(dialogs[0].peerID != this.myID) {
|
||||
dialogs.findAndSplice(d => d.peerID == this.myID);
|
||||
dialogs.unshift({
|
||||
peerID: this.myID,
|
||||
pFlags: {}
|
||||
} as any);
|
||||
}
|
||||
|
||||
this.renderResults(dialogs.map(dialog => dialog.peerID));
|
||||
});
|
||||
}
|
||||
|
||||
private async getMoreContacts() {
|
||||
if(this.promise) return this.promise;
|
||||
|
||||
if(!this.cachedContacts) {
|
||||
this.promise = appUsersManager.getContacts(this.query);
|
||||
this.cachedContacts = (await this.promise).slice();
|
||||
this.cachedContacts.findAndSplice(userID => userID == this.myID); // no my account
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
if(this.cachedContacts.length) {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
let arr = this.cachedContacts.splice(0, pageCount);
|
||||
this.renderResults(arr);
|
||||
}
|
||||
}
|
||||
|
||||
private getMoreResults() {
|
||||
if(this.peerType == 'dialogs') {
|
||||
this.getMoreDialogs();
|
||||
} else {
|
||||
this.getMoreContacts();
|
||||
}
|
||||
}
|
||||
|
||||
private renderResults(peerIDs: number[]) {
|
||||
peerIDs.forEach(peerID => {
|
||||
let {dom} = appDialogsManager.addDialog(peerID, this.list, false, false);
|
||||
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
|
||||
|
||||
let subtitle = '';
|
||||
if(peerID < 0) {
|
||||
subtitle = appChatsManager.getChatMembersString(-peerID);
|
||||
} else if(peerID == this.myID) {
|
||||
subtitle = 'chat with yourself';
|
||||
} else {
|
||||
subtitle = appUsersManager.getUserStatusString(peerID);
|
||||
if(subtitle == 'online') {
|
||||
subtitle = `<i>${subtitle}</i>`;
|
||||
}
|
||||
}
|
||||
|
||||
dom.lastMessageSpan.innerHTML = subtitle;
|
||||
});
|
||||
}
|
||||
|
||||
private add(peerID: number) {
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('selector-user', 'scale-in');
|
||||
div.dataset.peerID = '' + peerID;
|
||||
this.selected[peerID] = div;
|
||||
|
||||
let title = appPeersManager.getPeerTitle(peerID, false, true);
|
||||
|
||||
let avatarDiv = document.createElement('div');
|
||||
avatarDiv.classList.add('user-avatar', 'tgico');
|
||||
appProfileManager.putPhoto(avatarDiv, peerID);
|
||||
|
||||
div.innerHTML = title;
|
||||
div.insertAdjacentElement('afterbegin', avatarDiv);
|
||||
|
||||
this.selectedContainer.insertBefore(div, this.input);
|
||||
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
|
||||
this.onChange && this.onChange(Object.keys(this.selected).length);
|
||||
}
|
||||
|
||||
private remove(peerID: number) {
|
||||
let div = this.selected[peerID];
|
||||
div.classList.remove('scale-in');
|
||||
void div.offsetWidth;
|
||||
div.classList.add('scale-out');
|
||||
div.addEventListener('animationend', () => {
|
||||
delete this.selected[peerID];
|
||||
div.remove();
|
||||
this.onChange && this.onChange(Object.keys(this.selected).length);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
public getSelected() {
|
||||
return Object.keys(this.selected).map(p => +p);
|
||||
}
|
||||
}
|
200
src/components/poll.ts
Normal file
200
src/components/poll.ts
Normal file
@ -0,0 +1,200 @@
|
||||
import appPollsManager, { PollResults } from "../lib/appManagers/appPollsManager";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
|
||||
let lineTotalLength = 0;
|
||||
const tailLength = 9;
|
||||
const times = 10;
|
||||
const fullTime = 340;
|
||||
const oneTime = fullTime / times;
|
||||
|
||||
export default class PollElement extends HTMLElement {
|
||||
private svgLines: SVGSVGElement[];
|
||||
private numberDivs: HTMLDivElement[];
|
||||
private maxOffset = -44.8;
|
||||
private maxLength: number;
|
||||
private maxLengths: number[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// элемент создан
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// браузер вызывает этот метод при добавлении элемента в документ
|
||||
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
|
||||
|
||||
if(!lineTotalLength) {
|
||||
lineTotalLength = (document.getElementById('poll-line') as any as SVGPathElement).getTotalLength();
|
||||
console.log('line total length:', lineTotalLength);
|
||||
}
|
||||
|
||||
let pollID = this.getAttribute('poll-id');
|
||||
let {poll, results} = appPollsManager.getPoll(pollID);
|
||||
|
||||
console.log('pollElement poll:', poll, results);
|
||||
|
||||
let desc = '';
|
||||
if(poll.pFlags) {
|
||||
if(poll.pFlags.closed) {
|
||||
desc = 'Final results';
|
||||
} else {
|
||||
desc = poll.pFlags.public_voters ? 'Public Poll' : 'Anonymous Poll';
|
||||
}
|
||||
}
|
||||
|
||||
let votes = poll.answers.map(answer => {
|
||||
return `
|
||||
<div class="poll-answer">
|
||||
<div class="circle-hover">
|
||||
<div class="animation-ring"></div>
|
||||
<svg class="progress-ring">
|
||||
<circle class="progress-ring__circle" cx="13" cy="13" r="9"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="poll-answer-percents"></div>
|
||||
<svg version="1.1" class="poll-line" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 480 28" xml:space="preserve">
|
||||
<use href="#poll-line"></use>
|
||||
</svg>
|
||||
<div class="poll-answer-text">${RichTextProcessor.wrapEmojiText(answer.text)}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
this.innerHTML = `
|
||||
<div class="poll-title">${poll.rQuestion}</div>
|
||||
<div class="poll-desc">${desc}</div>
|
||||
${votes}
|
||||
<div class="poll-votes-count">${results.total_voters ? results.total_voters + ' voters' : 'No votes'}</div>
|
||||
`;
|
||||
|
||||
let width = this.getBoundingClientRect().width;
|
||||
this.maxLength = width + tailLength + this.maxOffset + -9; // 13 - position left
|
||||
this.performResults(results);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// браузер вызывает этот метод при удалении элемента из документа
|
||||
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
|
||||
}
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return [/* массив имён атрибутов для отслеживания их изменений */];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||
// вызывается при изменении одного из перечисленных выше атрибутов
|
||||
}
|
||||
|
||||
adoptedCallback() {
|
||||
// вызывается, когда элемент перемещается в новый документ
|
||||
// (происходит в document.adoptNode, используется очень редко)
|
||||
}
|
||||
|
||||
performResults(results: PollResults) {
|
||||
const percents = results.results.map(v => v.voters / results.total_voters * 100);
|
||||
this.setResults(percents);
|
||||
}
|
||||
|
||||
setResults(percents: number[]) {
|
||||
if(!this.svgLines) {
|
||||
this.svgLines = Array.from(this.querySelectorAll('.poll-line')) as SVGSVGElement[];
|
||||
this.numberDivs = Array.from(this.querySelectorAll('.poll-answer-percents')) as HTMLDivElement[];
|
||||
}
|
||||
|
||||
let maxValue = Math.max(...percents);
|
||||
|
||||
this.maxLengths = percents.map(p => p / maxValue * this.maxLength);
|
||||
|
||||
/* this.svgLines.forEach((svg, idx) => {
|
||||
this.setLineProgress(idx, 1);
|
||||
}); */
|
||||
|
||||
/* percents = percents.map(p => {
|
||||
return Math.round(p);
|
||||
}); */
|
||||
|
||||
console.log('setResults before percents:', percents);
|
||||
|
||||
let sum = percents.reduce((acc, p) => acc + Math.round(p), 0);
|
||||
if(sum > 100) {
|
||||
let diff = sum - 100;
|
||||
let length = percents.length;
|
||||
for(let i = 0; i < diff; ++i) {
|
||||
let minIndex = -1, minRemainder = 1;
|
||||
for(let k = 0; k < length; ++k) {
|
||||
let remainder = percents[k] % 1;
|
||||
if(remainder >= 0.5 && remainder < minRemainder) {
|
||||
minRemainder = remainder;
|
||||
minIndex = k;
|
||||
}
|
||||
}
|
||||
|
||||
if(minIndex == -1) {
|
||||
throw new Error('lol chto');
|
||||
}
|
||||
|
||||
percents[minIndex] -= minRemainder;
|
||||
}
|
||||
} else {
|
||||
let diff = 100 - sum;
|
||||
let length = percents.length;
|
||||
for(let i = 0; i < diff; ++i) {
|
||||
let minIndex = -1, maxRemainder = 0;
|
||||
for(let k = 0; k < length; ++k) {
|
||||
let remainder = percents[k] % 1;
|
||||
if(remainder < 0.5 && remainder > maxRemainder) {
|
||||
maxRemainder = remainder;
|
||||
minIndex = k;
|
||||
}
|
||||
}
|
||||
|
||||
if(minIndex == -1) {
|
||||
throw new Error('lol chto');
|
||||
}
|
||||
|
||||
percents[minIndex] += 1 - maxRemainder;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('setResults after percents:', percents, sum);
|
||||
|
||||
let start = Date.now();
|
||||
let r = () => {
|
||||
let diff = Date.now() - start;
|
||||
let progress = diff / fullTime;
|
||||
if(progress > 1) progress = 1;
|
||||
|
||||
this.svgLines.forEach((svg, idx) => {
|
||||
this.setLineProgress(idx, progress);
|
||||
});
|
||||
|
||||
if(progress < 1) {
|
||||
window.requestAnimationFrame(r);
|
||||
}
|
||||
};
|
||||
window.requestAnimationFrame(r);
|
||||
|
||||
for(let i = 0; i < times; ++i) {
|
||||
setTimeout(() => {
|
||||
percents.forEach((percents, idx) => {
|
||||
let value = Math.round(percents / times * (i + 1));
|
||||
let div = this.numberDivs[idx];
|
||||
//div.style.opacity = ((i + 1) * 0.10).toFixed(1); // опасити в 10 шагов от 0.1 до 1
|
||||
div.innerText = value + '%';
|
||||
});
|
||||
}, oneTime * i);
|
||||
}
|
||||
|
||||
this.classList.add('is-voted');
|
||||
}
|
||||
|
||||
setLineProgress(index: number, percents: number) {
|
||||
let svg = this.svgLines[index];
|
||||
svg.style.strokeDasharray = (percents * this.maxLengths[index]) + ', 485.9';
|
||||
svg.style.strokeDashoffset = '' + percents * this.maxOffset;
|
||||
}
|
||||
|
||||
// у элемента могут быть ещё другие методы и свойства
|
||||
}
|
||||
|
||||
customElements.define("poll-element", PollElement);
|
97
src/components/popupAvatar.ts
Normal file
97
src/components/popupAvatar.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import resizeableImage from "../lib/cropper";
|
||||
import apiFileManager from "../lib/mtproto/apiFileManager";
|
||||
|
||||
export class PopupAvatar {
|
||||
private container = document.getElementById('popup-avatar');
|
||||
private input = this.container.querySelector('input') as HTMLInputElement;
|
||||
private cropContainer = this.container.querySelector('.crop') as HTMLDivElement;
|
||||
private closeBtn = this.container.querySelector('.popup-close') as HTMLButtonElement;
|
||||
private image = new Image();
|
||||
|
||||
private canvas: HTMLCanvasElement;
|
||||
private blob: Blob;
|
||||
private cropper = {
|
||||
crop: () => {},
|
||||
removeHandlers: () => {}
|
||||
};
|
||||
|
||||
private onCrop: (upload: () => Promise<any>) => void;
|
||||
|
||||
constructor() {
|
||||
this.container.style.display = ''; // need for no blink
|
||||
this.cropContainer.append(this.image);
|
||||
|
||||
this.input.addEventListener('change', (e: any) => {
|
||||
var file = e.target.files[0];
|
||||
if(!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
var contents = e.target.result as string;
|
||||
|
||||
this.image = new Image();
|
||||
this.cropContainer.append(this.image);
|
||||
this.image.src = contents;
|
||||
|
||||
this.image.onload = () => {
|
||||
/* let {w, h} = calcImageInBox(this.image.naturalWidth, this.image.naturalHeight, 460, 554);
|
||||
cropContainer.style.width = w + 'px';
|
||||
cropContainer.style.height = h + 'px'; */
|
||||
this.container.classList.remove('hide');
|
||||
void this.container.offsetWidth; // reflow
|
||||
this.container.classList.add('active');
|
||||
|
||||
this.cropper = resizeableImage(this.image, this.canvas);
|
||||
this.input.value = '';
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}, false);
|
||||
|
||||
// apply
|
||||
this.container.querySelector('.btn-crop').addEventListener('click', () => {
|
||||
this.cropper.crop();
|
||||
this.closeBtn.click();
|
||||
|
||||
this.canvas.toBlob(blob => {
|
||||
this.blob = blob; // save blob to send after reg
|
||||
|
||||
// darken
|
||||
let ctx = this.canvas.getContext('2d');
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
||||
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.resolve();
|
||||
}, 'image/jpeg', 1);
|
||||
});
|
||||
|
||||
this.closeBtn.addEventListener('click', () => {
|
||||
setTimeout(() => {
|
||||
this.cropper.removeHandlers();
|
||||
if(this.image) {
|
||||
this.image.remove();
|
||||
}
|
||||
|
||||
this.container.classList.add('hide');
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
private resolve() {
|
||||
this.onCrop(() => {
|
||||
return apiFileManager.uploadFile(this.blob);
|
||||
});
|
||||
}
|
||||
|
||||
public open(postCanvas: HTMLCanvasElement, onCrop: (upload: () => Promise<any>) => void) {
|
||||
this.canvas = postCanvas;
|
||||
this.onCrop = onCrop;
|
||||
|
||||
this.input.click();
|
||||
}
|
||||
}
|
||||
|
||||
export default new PopupAvatar();
|
@ -15,6 +15,8 @@ import { CancellablePromise } from '../lib/polyfill';
|
||||
import { renderImageFromUrl } from './misc';
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import { Layouter, RectPart } from './groupedLayout';
|
||||
import { Poll, PollResults } from '../lib/appManagers/appPollsManager';
|
||||
import PollElement from './poll';
|
||||
|
||||
export type MTDocument = {
|
||||
_: 'document' | 'documentEmpty',
|
||||
@ -701,11 +703,11 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
|
||||
}
|
||||
|
||||
if(!stickerType) {
|
||||
console.error('wrong doc for wrapSticker!', doc, div);
|
||||
console.error('wrong doc for wrapSticker!', doc);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
console.log('wrap sticker', doc, div, onlyThumb);
|
||||
//console.log('wrap sticker', doc, div, onlyThumb);
|
||||
|
||||
if(doc.thumbs && !div.firstElementChild && (!doc.downloaded || stickerType == 2)) {
|
||||
let thumb = doc.thumbs[0];
|
||||
@ -875,6 +877,10 @@ export function wrapReply(title: string, subtitle: string, message?: any) {
|
||||
if(media) {
|
||||
if(message.grouped_id) {
|
||||
replySubtitle.innerHTML = 'Album';
|
||||
} else if(media._ == 'messageMediaContact') {
|
||||
replySubtitle.innerHTML = 'Contact';
|
||||
} else if(media._ == 'messageMediaPoll') {
|
||||
replySubtitle.innerHTML = media.poll.rReply;
|
||||
} else if(media.photo) {
|
||||
replySubtitle.innerHTML = 'Photo';
|
||||
} else if(media.document && media.document.type) {
|
||||
@ -946,7 +952,13 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
|
||||
let brSplitted = fillPropertyValue(borderRadius); */
|
||||
|
||||
for(let {geometry, sides} of layout) {
|
||||
let {size, media, message} = items.shift();
|
||||
let item = items.shift();
|
||||
if(!item) {
|
||||
console.error('no item for layout!');
|
||||
continue;
|
||||
}
|
||||
|
||||
let {size, media, message} = item;
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('album-item');
|
||||
div.dataset.mid = message.mid;
|
||||
@ -1041,3 +1053,9 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
|
||||
attachmentDiv.append(div);
|
||||
}
|
||||
}
|
||||
|
||||
export function wrapPoll(poll: Poll, results: PollResults) {
|
||||
let elem = new PollElement();
|
||||
elem.setAttribute('poll-id', poll.id);
|
||||
return elem;
|
||||
}
|
||||
|
68
src/format_schema.js
Normal file
68
src/format_schema.js
Normal file
@ -0,0 +1,68 @@
|
||||
let json = require('./schema');
|
||||
|
||||
let top = {};
|
||||
/* ['MTProto', 'API'].forEach(key => {
|
||||
let schema = json[key];
|
||||
let out = {constructors: {}, methods: {}};
|
||||
|
||||
['constructors', 'methods'].forEach(key => {
|
||||
schema[key].forEach(smth => {
|
||||
let id = smth.id;
|
||||
|
||||
if(id < 0) {
|
||||
id = +id + 4294967296;
|
||||
}
|
||||
|
||||
out[key][id] = smth;
|
||||
delete smth.id;
|
||||
});
|
||||
});
|
||||
|
||||
top[key] = out;
|
||||
|
||||
//console.log(out);
|
||||
//process.exit(0);
|
||||
}); */
|
||||
|
||||
['MTProto', 'API'].forEach(key => {
|
||||
let schema = json[key];
|
||||
|
||||
['constructors', 'methods'].forEach(key => {
|
||||
schema[key].forEach(smth => {
|
||||
if(+smth.id < 0) {
|
||||
smth.id = +smth.id + 4294967296;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//console.log(out);
|
||||
//process.exit(0);
|
||||
});
|
||||
top = json;
|
||||
|
||||
/* ['API'].forEach(key => {
|
||||
let schema = json[key];
|
||||
let out = {constructors: {}, methods: {}};
|
||||
|
||||
['constructors', 'methods'].forEach(key => {
|
||||
schema[key].forEach(smth => {
|
||||
let id = smth.id;
|
||||
|
||||
if(id < 0) {
|
||||
id = id + 4294967296;
|
||||
}
|
||||
|
||||
out[key][id] = smth;
|
||||
delete smth.id;
|
||||
});
|
||||
});
|
||||
|
||||
top[key] = out;
|
||||
|
||||
//console.log(out);
|
||||
//process.exit(0);
|
||||
}); */
|
||||
|
||||
//console.log(out);
|
||||
|
||||
require('fs').writeFileSync('./schema_pretty.json', JSON.stringify(top/* , null, '\t' */));
|
@ -1,9 +1,43 @@
|
||||
import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import appPeersManager from "./appPeersManager";
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import apiUpdatesManager from "./apiUpdatesManager";
|
||||
|
||||
type Channel = {
|
||||
_: 'channel',
|
||||
flags: number,
|
||||
pFlags: Partial<{
|
||||
creator: true,
|
||||
left: true,
|
||||
broadcast: true,
|
||||
verified: true,
|
||||
megagroup: true,
|
||||
restricted: true,
|
||||
signatures: true,
|
||||
min: true,
|
||||
scam: true,
|
||||
has_link: true,
|
||||
has_geo: true,
|
||||
slowmode_enabled: true
|
||||
}>,
|
||||
id: number,
|
||||
access_hash?: string,
|
||||
title: string,
|
||||
username?: string,
|
||||
photo: any,
|
||||
date: number,
|
||||
version: number,
|
||||
restriction_reason?: any,
|
||||
admin_rights?: any,
|
||||
banned_rights?: any,
|
||||
default_banned_rights?: any,
|
||||
participants_count: number
|
||||
};
|
||||
|
||||
export class AppChatsManager {
|
||||
public chats: any = {};
|
||||
public chats: {[id: number]: Channel | any} = {};
|
||||
public usernames: any = {};
|
||||
public channelAccess: any = {};
|
||||
public megagroups: any = {};
|
||||
@ -143,8 +177,7 @@ export class AppChatsManager {
|
||||
|
||||
public isChannel(id: number) {
|
||||
var chat = this.chats[id];
|
||||
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') ||
|
||||
this.channelAccess[id]) {
|
||||
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') || this.channelAccess[id]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -166,10 +199,6 @@ export class AppChatsManager {
|
||||
return this.isChannel(id) && !this.isMegagroup(id);
|
||||
}
|
||||
|
||||
public getChatInput(id: number) {
|
||||
return id || 0;
|
||||
}
|
||||
|
||||
public getChannelInput(id: number) {
|
||||
if(!id) {
|
||||
return {_: 'inputChannelEmpty'};
|
||||
@ -182,6 +211,25 @@ export class AppChatsManager {
|
||||
};
|
||||
}
|
||||
|
||||
public getChatInputPeer(id: number) {
|
||||
return {
|
||||
_: 'inputPeerChat',
|
||||
chat_id: id
|
||||
};
|
||||
}
|
||||
|
||||
public getChannelInputPeer(id: number) {
|
||||
if(!id) {
|
||||
return {_: 'inputPeerEmpty'};
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'inputPeerChannel',
|
||||
channel_id: id,
|
||||
access_hash: this.getChat(id).access_hash || this.channelAccess[id] || 0
|
||||
};
|
||||
}
|
||||
|
||||
public hasChat(id: number, allowMin?: any) {
|
||||
var chat = this.chats[id]
|
||||
return isObject(chat) && (allowMin || !chat.pFlags.min);
|
||||
@ -217,7 +265,7 @@ export class AppChatsManager {
|
||||
var chatFull = copy(fullChat);
|
||||
var chat = this.getChat(id);
|
||||
|
||||
if (!chatFull.participants_count) {
|
||||
if(!chatFull.participants_count) {
|
||||
chatFull.participants_count = chat.participants_count;
|
||||
}
|
||||
|
||||
@ -226,7 +274,7 @@ export class AppChatsManager {
|
||||
chatFull.participants.participants = this.wrapParticipants(id, chatFull.participants.participants);
|
||||
}
|
||||
|
||||
if (chatFull.about) {
|
||||
if(chatFull.about) {
|
||||
chatFull.rAbout = RichTextProcessor.wrapRichText(chatFull.about, {noLinebreaks: true});
|
||||
}
|
||||
|
||||
@ -240,7 +288,7 @@ export class AppChatsManager {
|
||||
var chat = this.getChat(id);
|
||||
var myID = appUsersManager.getSelf().id;
|
||||
if(this.isChannel(id)) {
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator;
|
||||
participants.forEach((participant) => {
|
||||
participant.canLeave = myID == participant.user_id;
|
||||
participant.canKick = isAdmin && participant._ == 'channelParticipant';
|
||||
@ -249,7 +297,7 @@ export class AppChatsManager {
|
||||
participant.user = appUsersManager.getUser(participant.user_id);
|
||||
});
|
||||
} else {
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin
|
||||
var isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin;
|
||||
participants.forEach((participant) => {
|
||||
participant.canLeave = myID == participant.user_id;
|
||||
participant.canKick = !participant.canLeave && (
|
||||
@ -261,8 +309,70 @@ export class AppChatsManager {
|
||||
participant.user = appUsersManager.getUser(participant.user_id);
|
||||
});
|
||||
}
|
||||
|
||||
return participants;
|
||||
}
|
||||
|
||||
public createChannel(title: string, about: String): Promise<number> {
|
||||
return apiManager.invokeApi('channels.createChannel', {
|
||||
flags: 1,
|
||||
broadcast: true,
|
||||
title: title,
|
||||
about: about
|
||||
}).then((updates: any) => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
|
||||
return updates.chats[0].id;
|
||||
});
|
||||
}
|
||||
|
||||
public inviteToChannel(id: number, userIDs: number[]) {
|
||||
let input = this.getChannelInput(id);
|
||||
let usersInputs = userIDs.map(u => appUsersManager.getUserInput(u));
|
||||
|
||||
return apiManager.invokeApi('channels.inviteToChannel', {
|
||||
channel: input,
|
||||
users: usersInputs
|
||||
}).then(updates => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
}
|
||||
|
||||
public createChat(title: string, userIDs: number[]): Promise<number> {
|
||||
return apiManager.invokeApi('messages.createChat', {
|
||||
users: userIDs.map(u => appUsersManager.getUserInput(u)),
|
||||
title: title
|
||||
}).then(updates => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
|
||||
return updates.chats[0].id;
|
||||
});
|
||||
}
|
||||
|
||||
public editPhoto(id: number, inputFile: any) {
|
||||
let isChannel = this.isChannel(id);
|
||||
|
||||
let inputChatPhoto = {
|
||||
_: 'inputChatUploadedPhoto',
|
||||
file: inputFile
|
||||
};
|
||||
|
||||
if(isChannel) {
|
||||
return apiManager.invokeApi('channels.editPhoto', {
|
||||
channel: this.getChannelInputPeer(id),
|
||||
photo: inputChatPhoto
|
||||
}).then(updates => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
} else {
|
||||
return apiManager.invokeApi('messages.editChatPhoto', {
|
||||
chat_id: id,
|
||||
photo: inputChatPhoto
|
||||
}).then(updates => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppChatsManager();
|
||||
|
@ -9,6 +9,7 @@ import { ripple, putPreloader } from "../../components/misc";
|
||||
import Scrollable from "../../components/scrollable_new";
|
||||
import appProfileManager from "./appProfileManager";
|
||||
import { logger } from "../polyfill";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
|
||||
type DialogDom = {
|
||||
avatarDiv: HTMLDivElement,
|
||||
@ -474,6 +475,12 @@ export class AppDialogsManager {
|
||||
case 'messageMediaGeo':
|
||||
lastMessageText += '<i>Geolocation</i>';
|
||||
break;
|
||||
case 'messageMediaPoll':
|
||||
lastMessageText += '<i>' + lastMessage.media.poll.rReply + '</i>';
|
||||
break;
|
||||
case 'messageMediaContact':
|
||||
lastMessageText += '<i>Contact</i>';
|
||||
break;
|
||||
case 'messageMediaDocument':
|
||||
let document = lastMessage.media.document;
|
||||
|
||||
@ -724,6 +731,18 @@ export class AppDialogsManager {
|
||||
let titleSpan = document.createElement('span');
|
||||
titleSpan.classList.add('user-title');
|
||||
|
||||
if(peerID < 0) {
|
||||
let chat = appChatsManager.getChat(-peerID);
|
||||
if(chat && chat.pFlags && chat.pFlags.verified) {
|
||||
titleSpan.classList.add('is-verified');
|
||||
}
|
||||
} else {
|
||||
let user = appUsersManager.getUser(peerID);
|
||||
if(user && user.pFlags && user.pFlags.verified) {
|
||||
titleSpan.classList.add('is-verified');
|
||||
}
|
||||
}
|
||||
|
||||
if(peerID == $rootScope.myID) {
|
||||
title = onlyFirstName ? 'Saved' : 'Saved Messages';
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack } from "../utils";
|
||||
import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack } from "../utils";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import appMessagesManager from "./appMessagesManager";
|
||||
import appPeersManager from "./appPeersManager";
|
||||
@ -17,7 +17,7 @@ import appSidebarLeft from "./appSidebarLeft";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
import appMessagesIDsManager from "./appMessagesIDsManager";
|
||||
import apiUpdatesManager from './apiUpdatesManager';
|
||||
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum } from '../../components/wrappers';
|
||||
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
|
||||
import ProgressivePreloader from '../../components/preloader';
|
||||
import { openBtnMenu, formatPhoneNumber } from '../../components/misc';
|
||||
import { ChatInput } from '../../components/chatInput';
|
||||
@ -838,8 +838,21 @@ export class AppImManager {
|
||||
let [chatOnlines, chatInfo] = results;
|
||||
|
||||
let onlines = chatOnlines ? chatOnlines.onlines : 1;
|
||||
|
||||
if(chatInfo._ == 'chatFull' && chatInfo.participants && chatInfo.participants.participants) {
|
||||
let participants = chatInfo.participants.participants;
|
||||
|
||||
onlines = participants.reduce((acc: number, participant: any) => {
|
||||
let user = appUsersManager.getUser(participant.user_id);
|
||||
if(user && user.status && user.status._ == 'userStatusOnline') {
|
||||
return acc + 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
///////////this.log('chatInfo res:', chatInfo);
|
||||
this.log('chatInfo res:', chatInfo);
|
||||
|
||||
if(chatInfo.pinned_msg_id) { // request pinned message
|
||||
this.pinnedMsgID = chatInfo.pinned_msg_id;
|
||||
@ -882,6 +895,9 @@ export class AppImManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.subtitleEl.innerText = 'bot';
|
||||
appSidebarRight.profileElements.subtitle.innerText = 'bot';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1058,7 +1074,7 @@ export class AppImManager {
|
||||
this.scroll.scrollTop = this.scroll.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* this.onScroll();
|
||||
this.scrollable.onScroll();*/
|
||||
|
||||
@ -1225,7 +1241,8 @@ export class AppImManager {
|
||||
|
||||
// reverse means top
|
||||
public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) {
|
||||
//this.log('message to render:', message);
|
||||
this.log('message to render:', message);
|
||||
//return;
|
||||
if(message.deleted) return;
|
||||
else if(message.grouped_id) { // will render only last album's message
|
||||
let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id];
|
||||
@ -1278,8 +1295,14 @@ export class AppImManager {
|
||||
if(_ == "messageActionPhoneCall") {
|
||||
_ += '.' + action.type;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
let str = (name.innerText ? name.outerHTML + ' ' : '') + langPack[_];
|
||||
let l = langPack[_];
|
||||
if(!l) {
|
||||
l = '[' + _ + ']';
|
||||
}
|
||||
|
||||
let str = l[0].toUpperCase() == l[0] ? l : (name.innerText ? name.outerHTML + ' ' : '') + l;
|
||||
bubbleContainer.innerHTML = `<div class="service-msg">${str}</div>`;
|
||||
|
||||
/* if(!updatePosition) {
|
||||
@ -1401,7 +1424,7 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
// media
|
||||
if(message.media) {
|
||||
if(message.media/* && message.media._ == 'messageMediaPhoto' */) {
|
||||
let attachmentDiv = document.createElement('div');
|
||||
attachmentDiv.classList.add('attachment');
|
||||
|
||||
@ -1712,6 +1735,15 @@ export class AppImManager {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'messageMediaPoll': {
|
||||
bubble.classList.remove('is-message-empty');
|
||||
|
||||
let pollElement = wrapPoll(message.media.poll, message.media.results);
|
||||
messageDiv.prepend(pollElement);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
bubble.classList.remove('is-message-empty');
|
||||
@ -1877,7 +1909,7 @@ export class AppImManager {
|
||||
let _history = history.slice();
|
||||
setTimeout(() => {
|
||||
this.performHistoryResult(_history, reverse, isBackLimit, 0, resetPromises);
|
||||
}, (i + 1) * 2500);
|
||||
}, 0/* (i + 1) * 2500 */);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2092,8 +2124,8 @@ export class AppImManager {
|
||||
|
||||
this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids && ids.length);
|
||||
|
||||
let removeCount = loadCount / 2;
|
||||
let safeCount = realLoadCount * 2;
|
||||
//let removeCount = loadCount / 2;
|
||||
let safeCount = Math.min(realLoadCount * 2, 35); // cause i've been runningrunningrunning all day
|
||||
if(ids && ids.length > safeCount) {
|
||||
if(reverse) {
|
||||
//ids = ids.slice(-removeCount);
|
||||
@ -2210,49 +2242,51 @@ export class AppImManager {
|
||||
public handleUpdate(update: any) {
|
||||
switch(update._) {
|
||||
case 'updateUserTyping':
|
||||
case 'updateChatUserTyping':
|
||||
if(this.myID == update.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
|
||||
this.typingUsers[update.user_id] = peerID;
|
||||
|
||||
if(!appUsersManager.hasUser(update.user_id)) {
|
||||
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
|
||||
appProfileManager.getChatFull(update.chat_id);
|
||||
case 'updateChatUserTyping': {
|
||||
if(this.myID == update.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
appUsersManager.forceUserOnline(update.user_id);
|
||||
|
||||
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
|
||||
let currentPeer = this.peerID == peerID;
|
||||
|
||||
if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
|
||||
else if(dialog) {
|
||||
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
|
||||
var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
|
||||
this.typingUsers[update.user_id] = peerID;
|
||||
|
||||
if(currentPeer) { // user
|
||||
if(!appUsersManager.hasUser(update.user_id)) {
|
||||
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
|
||||
appProfileManager.getChatFull(update.chat_id);
|
||||
}
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
appUsersManager.forceUserOnline(update.user_id);
|
||||
|
||||
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
|
||||
let currentPeer = this.peerID == peerID;
|
||||
|
||||
if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
|
||||
else if(dialog) {
|
||||
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
|
||||
|
||||
if(currentPeer) { // user
|
||||
this.setPeerStatus();
|
||||
}
|
||||
}
|
||||
|
||||
this.typingTimeouts[peerID] = setTimeout(() => {
|
||||
this.typingTimeouts[peerID] = 0;
|
||||
delete this.typingUsers[update.user_id];
|
||||
|
||||
if(dialog) {
|
||||
appDialogsManager.unsetTyping(dialog);
|
||||
}
|
||||
|
||||
// лень просчитывать случаи
|
||||
this.setPeerStatus();
|
||||
}
|
||||
}, 6000);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.typingTimeouts[peerID] = setTimeout(() => {
|
||||
this.typingTimeouts[peerID] = 0;
|
||||
delete this.typingUsers[update.user_id];
|
||||
|
||||
if(dialog) {
|
||||
appDialogsManager.unsetTyping(dialog);
|
||||
}
|
||||
|
||||
// лень просчитывать случаи
|
||||
this.setPeerStatus();
|
||||
}, 6000);
|
||||
break;
|
||||
|
||||
case 'updateNotifySettings': {
|
||||
let {peer, notify_settings} = update;
|
||||
|
||||
|
@ -21,6 +21,7 @@ import serverTimeManager from "../mtproto/serverTimeManager";
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import appWebPagesManager from "./appWebPagesManager";
|
||||
import { CancellablePromise, deferredPromise } from "../polyfill";
|
||||
import appPollsManager from "./appPollsManager";
|
||||
|
||||
const APITIMEOUT = 0;
|
||||
|
||||
@ -1501,7 +1502,10 @@ export class AppMessagesManager {
|
||||
apiMessage.media.photo = appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext);
|
||||
//appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'messageMediaPoll':
|
||||
appPollsManager.savePoll(apiMessage.media.poll, apiMessage.media.results);
|
||||
break;
|
||||
case 'messageMediaDocument':
|
||||
if(apiMessage.media.ttl_seconds) {
|
||||
apiMessage.media = {_: 'messageMediaUnsupportedWeb'};
|
||||
|
@ -30,10 +30,12 @@ const AppPeersManager = {
|
||||
if(peerID >= 0) {
|
||||
return false;
|
||||
}
|
||||
var chat = appChatsManager.getChat(-peerID);
|
||||
|
||||
let chat = appChatsManager.getChat(-peerID);
|
||||
if(chat && chat.migrated_to && chat.pFlags.deactivated) {
|
||||
return AppPeersManager.getPeerID(chat.migrated_to);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -66,10 +68,11 @@ const AppPeersManager = {
|
||||
return {_: 'peerUser', user_id: peerID};
|
||||
}
|
||||
|
||||
var chatID = -peerID;
|
||||
let chatID = -peerID;
|
||||
if(appChatsManager.isChannel(chatID)) {
|
||||
return {_: 'peerChannel', channel_id: chatID};
|
||||
}
|
||||
|
||||
return {_: 'peerChat', chat_id: chatID};
|
||||
},
|
||||
|
||||
@ -99,8 +102,8 @@ const AppPeersManager = {
|
||||
? peerString.user_id
|
||||
: -(peerString.channel_id || peerString.chat_id);
|
||||
}
|
||||
var isUser = peerString.charAt(0) == 'u';
|
||||
var peerParams = peerString.substr(1).split('_');
|
||||
let isUser = peerString.charAt(0) == 'u';
|
||||
let peerParams = peerString.substr(1).split('_');
|
||||
|
||||
return isUser ? peerParams[0] : -peerParams[0] || 0;
|
||||
},
|
||||
@ -125,25 +128,52 @@ const AppPeersManager = {
|
||||
return (peerID > 0) && appUsersManager.isBot(peerID);
|
||||
},
|
||||
|
||||
getInputPeerByID: (peerID: number) => {
|
||||
if (!peerID) {
|
||||
return {_: 'inputPeerEmpty'}
|
||||
getInputPeer: (peerString: string): any => {
|
||||
var firstChar = peerString.charAt(0);
|
||||
var peerParams = peerString.substr(1).split('_');
|
||||
let id = +peerParams[0];
|
||||
|
||||
if(firstChar == 'u') {
|
||||
appUsersManager.saveUserAccess(id, peerParams[1]);
|
||||
|
||||
return {
|
||||
_: 'inputPeerUser',
|
||||
user_id: id,
|
||||
access_hash: peerParams[1]
|
||||
};
|
||||
} else if(firstChar == 'c' || firstChar == 's') {
|
||||
appChatsManager.saveChannelAccess(id, peerParams[1]);
|
||||
if(firstChar == 's') {
|
||||
appChatsManager.saveIsMegagroup(id);
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'inputPeerChannel',
|
||||
channel_id: id,
|
||||
access_hash: peerParams[1] || 0
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
_: 'inputPeerChat',
|
||||
chat_id: id
|
||||
};
|
||||
}
|
||||
if (peerID < 0) {
|
||||
var chatID = -peerID
|
||||
if (!appChatsManager.isChannel(chatID)) {
|
||||
return {
|
||||
_: 'inputPeerChat',
|
||||
chat_id: chatID
|
||||
};
|
||||
},
|
||||
|
||||
getInputPeerByID: (peerID: number) => {
|
||||
if(!peerID) {
|
||||
return {_: 'inputPeerEmpty'};
|
||||
}
|
||||
|
||||
if(peerID < 0) {
|
||||
let chatID = -peerID;
|
||||
if(!appChatsManager.isChannel(chatID)) {
|
||||
return appChatsManager.getChatInputPeer(chatID);
|
||||
} else {
|
||||
return {
|
||||
_: 'inputPeerChannel',
|
||||
channel_id: chatID,
|
||||
access_hash: appChatsManager.getChat(chatID).access_hash || 0
|
||||
};
|
||||
return appChatsManager.getChannelInputPeer(chatID);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'inputPeerUser',
|
||||
user_id: peerID,
|
||||
@ -158,11 +188,11 @@ const AppPeersManager = {
|
||||
},
|
||||
|
||||
getPeerSearchText: (peerID: number) => {
|
||||
var text
|
||||
let text;
|
||||
if(peerID > 0) {
|
||||
text = '%pu ' + appUsersManager.getUserSearchText(peerID);
|
||||
} else if(peerID < 0) {
|
||||
var chat = appChatsManager.getChat(-peerID);
|
||||
let chat = appChatsManager.getChat(-peerID);
|
||||
text = '%pg ' + (chat.title || '');
|
||||
}
|
||||
return text;
|
||||
|
89
src/lib/appManagers/appPollsManager.ts
Normal file
89
src/lib/appManagers/appPollsManager.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
|
||||
export type PollAnswer = {
|
||||
_: 'pollAnswer',
|
||||
text: string,
|
||||
option: Uint8Array
|
||||
};
|
||||
|
||||
export type PollAnswerVoters = {
|
||||
_: 'pollAnswerVoters',
|
||||
flags: number,
|
||||
option: Uint8Array,
|
||||
voters: number,
|
||||
|
||||
pFlags: Partial<{
|
||||
chosen: true,
|
||||
correct: true
|
||||
}>
|
||||
};
|
||||
|
||||
export type PollResult = {
|
||||
_: 'pollAnswerVoters',
|
||||
flags: number,
|
||||
option: Uint8Array,
|
||||
voters: number,
|
||||
|
||||
pFlags?: Partial<{chosen: true}>
|
||||
};
|
||||
|
||||
export type PollResults = {
|
||||
_: 'pollResults',
|
||||
flags: number,
|
||||
results?: Array<PollResult>,
|
||||
total_voters?: number,
|
||||
recent_voters?: number[],
|
||||
solution?: string,
|
||||
solution_entities?: any[],
|
||||
|
||||
pFlags: Partial<{
|
||||
min: true
|
||||
}>,
|
||||
};
|
||||
|
||||
export type Poll = {
|
||||
_: 'poll',
|
||||
flags: number,
|
||||
question: string,
|
||||
id: string,
|
||||
answers: Array<PollAnswer>,
|
||||
close_period?: number,
|
||||
close_date?: number
|
||||
|
||||
pFlags?: Partial<{
|
||||
closed: true,
|
||||
public_voters: true,
|
||||
multiple_choice: true,
|
||||
quiz: true
|
||||
}>,
|
||||
rQuestion?: string,
|
||||
rReply?: string,
|
||||
};
|
||||
|
||||
class AppPollsManager {
|
||||
private polls: {[id: string]: Poll} = {};
|
||||
private results: {[id: string]: PollResults} = {};
|
||||
|
||||
public savePoll(poll: Poll, results: PollResults) {
|
||||
let id = poll.id;
|
||||
if(this.polls[id]) {
|
||||
this.results[id] = results;
|
||||
return;
|
||||
}
|
||||
|
||||
this.polls[id] = poll;
|
||||
this.results[id] = results;
|
||||
|
||||
poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question);
|
||||
poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll');
|
||||
}
|
||||
|
||||
public getPoll(pollID: string): {poll: Poll, results: PollResults} {
|
||||
return {
|
||||
poll: this.polls[pollID],
|
||||
results: this.results[pollID]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppPollsManager();
|
@ -5,17 +5,318 @@ import appImManager from "./appImManager";
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import AppSearch, { SearchGroup } from "../../components/appSearch";
|
||||
import { horizontalMenu } from "../../components/misc";
|
||||
import { horizontalMenu, putPreloader } from "../../components/misc";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import Scrollable from "../../components/scrollable_new";
|
||||
import appPhotosManager from "./appPhotosManager";
|
||||
import { appPeersManager } from "../services";
|
||||
import popupAvatar from "../../components/popupAvatar";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
import { AppSelectPeers } from "../../components/appSelectPeers";
|
||||
|
||||
const SLIDERITEMSIDS = {
|
||||
archived: 1,
|
||||
contacts: 2
|
||||
contacts: 2,
|
||||
newChannel: 3,
|
||||
addMembers: 4,
|
||||
newGroup: 5,
|
||||
};
|
||||
|
||||
interface SliderTab {
|
||||
onClose?: () => void,
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
class AppAddMembersTab implements SliderTab {
|
||||
private container = document.querySelector('.addmembers-container') as HTMLDivElement;
|
||||
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
|
||||
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
|
||||
private nextBtn = this.contentDiv.querySelector('.btn-corner') as HTMLButtonElement;
|
||||
private selector: AppSelectPeers;
|
||||
private peerType: 'channel' | 'chat';
|
||||
private peerID: number; // always positive
|
||||
private takeOut: (peerIDs: number[]) => void
|
||||
|
||||
constructor() {
|
||||
this.nextBtn.addEventListener('click', () => {
|
||||
let peerIDs = this.selector.getSelected();
|
||||
|
||||
if(peerIDs.length) {
|
||||
if(this.takeOut) {
|
||||
this.takeOut(peerIDs);
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextBtn.classList.remove('tgico-next');
|
||||
this.nextBtn.disabled = true;
|
||||
putPreloader(this.nextBtn);
|
||||
this.selector.freezed = true;
|
||||
|
||||
appChatsManager.inviteToChannel(this.peerID, peerIDs).then(() => {
|
||||
this.backBtn.click();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
if(this.selector) {
|
||||
this.selector.container.remove();
|
||||
this.selector = null;
|
||||
}
|
||||
}
|
||||
|
||||
public init(id: number, type: 'channel' | 'chat', skipable: boolean, takeOut?: AppAddMembersTab['takeOut']) {
|
||||
this.peerID = Math.abs(id);
|
||||
this.peerType = type;
|
||||
this.takeOut = takeOut;
|
||||
|
||||
this.onCloseAfterTimeout();
|
||||
this.selector = new AppSelectPeers(this.contentDiv, skipable ? null : (length) => {
|
||||
if(length) {
|
||||
this.nextBtn.classList.add('is-visible');
|
||||
} else {
|
||||
this.nextBtn.classList.remove('is-visible');
|
||||
}
|
||||
}, 'contacts');
|
||||
|
||||
this.nextBtn.innerHTML = '';
|
||||
this.nextBtn.disabled = false;
|
||||
this.nextBtn.classList.add('tgico-next');
|
||||
if(skipable) {
|
||||
this.nextBtn.classList.add('is-visible');
|
||||
} else {
|
||||
this.nextBtn.classList.remove('is-visible');
|
||||
}
|
||||
|
||||
appSidebarLeft.selectTab(SLIDERITEMSIDS.addMembers);
|
||||
}
|
||||
}
|
||||
|
||||
class AppNewChannelTab implements SliderTab {
|
||||
private container = document.querySelector('.new-channel-container') as HTMLDivElement;
|
||||
private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement;
|
||||
private channelNameInput = this.container.querySelector('.new-channel-name') as HTMLInputElement;
|
||||
private channelDescriptionInput = this.container.querySelector('.new-channel-description') as HTMLInputElement;
|
||||
private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement;
|
||||
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
|
||||
private uploadAvatar: () => Promise<any> = null;
|
||||
|
||||
constructor() {
|
||||
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
|
||||
popupAvatar.open(this.canvas, (_upload) => {
|
||||
this.uploadAvatar = _upload;
|
||||
});
|
||||
});
|
||||
|
||||
this.channelNameInput.addEventListener('input', () => {
|
||||
let value = this.channelNameInput.value;
|
||||
if(value.length) {
|
||||
this.nextBtn.classList.add('is-visible');
|
||||
} else {
|
||||
this.nextBtn.classList.remove('is-visible');
|
||||
}
|
||||
});
|
||||
|
||||
this.nextBtn.addEventListener('click', () => {
|
||||
let title = this.channelNameInput.value;
|
||||
let about = this.channelDescriptionInput.value;
|
||||
|
||||
this.nextBtn.disabled = true;
|
||||
appChatsManager.createChannel(title, about).then((channelID) => {
|
||||
if(this.uploadAvatar) {
|
||||
this.uploadAvatar().then((inputFile: any) => {
|
||||
appChatsManager.editPhoto(channelID, inputFile);
|
||||
});
|
||||
}
|
||||
|
||||
appSidebarLeft.removeTabFromHistory(SLIDERITEMSIDS.newChannel);
|
||||
appSidebarLeft.addMembersTab.init(channelID, 'channel', true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
let ctx = this.canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.uploadAvatar = null;
|
||||
this.channelNameInput.value = '';
|
||||
this.channelDescriptionInput.value = '';
|
||||
this.nextBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
class AppNewGroupTab implements SliderTab {
|
||||
private container = document.querySelector('.new-group-container') as HTMLDivElement;
|
||||
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
|
||||
private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement;
|
||||
private groupNameInput = this.container.querySelector('.new-group-name') as HTMLInputElement;
|
||||
private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement;
|
||||
private searchGroup = new SearchGroup('', 'contacts', true, 'new-group-members disable-hover', false);
|
||||
private uploadAvatar: () => Promise<any> = null;
|
||||
private userIDs: number[];
|
||||
|
||||
constructor() {
|
||||
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
|
||||
popupAvatar.open(this.canvas, (_upload) => {
|
||||
this.uploadAvatar = _upload;
|
||||
});
|
||||
});
|
||||
|
||||
this.groupNameInput.addEventListener('input', () => {
|
||||
let value = this.groupNameInput.value;
|
||||
if(value.length) {
|
||||
this.nextBtn.classList.add('is-visible');
|
||||
} else {
|
||||
this.nextBtn.classList.remove('is-visible');
|
||||
}
|
||||
});
|
||||
|
||||
this.nextBtn.addEventListener('click', () => {
|
||||
let title = this.groupNameInput.value;
|
||||
|
||||
this.nextBtn.disabled = true;
|
||||
appChatsManager.createChat(title, this.userIDs).then((chatID) => {
|
||||
if(this.uploadAvatar) {
|
||||
this.uploadAvatar().then((inputFile: any) => {
|
||||
appChatsManager.editPhoto(chatID, inputFile);
|
||||
});
|
||||
}
|
||||
|
||||
appSidebarLeft.selectTab(0);
|
||||
});
|
||||
});
|
||||
|
||||
let chatsContainer = document.createElement('div');
|
||||
chatsContainer.classList.add('chats-container');
|
||||
chatsContainer.append(this.searchGroup.container);
|
||||
|
||||
let scrollable = new Scrollable(chatsContainer);
|
||||
|
||||
this.contentDiv.append(chatsContainer);
|
||||
}
|
||||
|
||||
public onClose() {
|
||||
|
||||
}
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
this.searchGroup.clear();
|
||||
|
||||
let ctx = this.canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.uploadAvatar = null;
|
||||
this.groupNameInput.value = '';
|
||||
this.nextBtn.disabled = false;
|
||||
}
|
||||
|
||||
public init(userIDs: number[]) {
|
||||
this.userIDs = userIDs;
|
||||
|
||||
appSidebarLeft.selectTab(SLIDERITEMSIDS.newGroup);
|
||||
this.userIDs.forEach(userID => {
|
||||
let {dom} = appDialogsManager.addDialog(userID, this.searchGroup.list, false, false);
|
||||
|
||||
let subtitle = '';
|
||||
subtitle = appUsersManager.getUserStatusString(userID);
|
||||
if(subtitle == 'online') {
|
||||
subtitle = `<i>${subtitle}</i>`;
|
||||
}
|
||||
|
||||
if(subtitle) {
|
||||
dom.lastMessageSpan.innerHTML = subtitle;
|
||||
}
|
||||
});
|
||||
|
||||
this.searchGroup.nameEl.innerText = this.userIDs.length + ' members';
|
||||
this.searchGroup.setActive();
|
||||
}
|
||||
}
|
||||
|
||||
class AppContactsTab implements SliderTab {
|
||||
private container = document.getElementById('contacts-container');
|
||||
private list = this.container.querySelector('#contacts') as HTMLUListElement;
|
||||
private scrollable: Scrollable;
|
||||
private promise: Promise<void>;
|
||||
private input = this.container.querySelector('#contacts-search') as HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
appDialogsManager.setListClickListener(this.list);
|
||||
this.scrollable = new Scrollable(this.list.parentElement);
|
||||
|
||||
let prevValue = '';
|
||||
this.input.addEventListener('input', () => {
|
||||
let value = this.input.value;
|
||||
if(prevValue != value) {
|
||||
this.list.innerHTML = '';
|
||||
this.openContacts(prevValue = value);
|
||||
}
|
||||
});
|
||||
|
||||
// preload contacts
|
||||
appUsersManager.getContacts();
|
||||
}
|
||||
|
||||
// need to clear, and left 1 page for smooth slide
|
||||
public onClose() {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
(Array.from(this.list.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove());
|
||||
}
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
this.list.innerHTML = '';
|
||||
}
|
||||
|
||||
public openContacts(query?: string) {
|
||||
appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts);
|
||||
if(this.promise) return this.promise;
|
||||
this.scrollable.onScrolledBottom = null;
|
||||
|
||||
this.promise = appUsersManager.getContacts(query).then(contacts => {
|
||||
this.promise = null;
|
||||
|
||||
if(appSidebarLeft.historyTabIDs[appSidebarLeft.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) {
|
||||
console.warn('user closed contacts before it\'s loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted = contacts
|
||||
.map(userID => {
|
||||
let user = appUsersManager.getUser(userID);
|
||||
let status = appUsersManager.getUserStatusForSort(user.status);
|
||||
|
||||
return {user, status};
|
||||
})
|
||||
.sort((a, b) => b.status - a.status);
|
||||
|
||||
let renderPage = () => {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
let arr = sorted.splice(0, pageCount); // надо splice!
|
||||
|
||||
arr.forEach(({user}) => {
|
||||
let {dialog, dom} = appDialogsManager.addDialog(user.id, this.list, false);
|
||||
|
||||
let status = appUsersManager.getUserStatusString(user.id);
|
||||
dom.lastMessageSpan.innerHTML = status == 'online' ? `<i>${status}</i>` : status;
|
||||
});
|
||||
|
||||
if(!sorted.length) renderPage = undefined;
|
||||
};
|
||||
|
||||
renderPage();
|
||||
this.scrollable.onScrolledBottom = () => {
|
||||
if(renderPage) {
|
||||
renderPage();
|
||||
} else {
|
||||
this.scrollable.onScrolledBottom = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class AppSidebarLeft {
|
||||
private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
|
||||
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
|
||||
@ -24,12 +325,32 @@ class AppSidebarLeft {
|
||||
private searchInput = document.getElementById('global-search') as HTMLInputElement;
|
||||
|
||||
private menuEl = this.toolsBtn.querySelector('.btn-menu');
|
||||
private savedBtn = this.menuEl.querySelector('.menu-saved');
|
||||
private archivedBtn = this.menuEl.querySelector('.menu-archive');
|
||||
private newGroupBtn = this.menuEl.querySelector('.menu-new-group');
|
||||
private contactsBtn = this.menuEl.querySelector('.menu-contacts');
|
||||
private archivedBtn = this.menuEl.querySelector('.menu-archive');
|
||||
private savedBtn = this.menuEl.querySelector('.menu-saved');
|
||||
private logOutBtn = this.menuEl.querySelector('.menu-logout');
|
||||
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
|
||||
|
||||
private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
|
||||
private newButtons = {
|
||||
channel: this.newBtnMenu.querySelector('.menu-channel'),
|
||||
group: this.newBtnMenu.querySelector('.menu-group'),
|
||||
privateChat: this.newBtnMenu.querySelector('.menu-private-chat'),
|
||||
};
|
||||
|
||||
public newChannelTab = new AppNewChannelTab();
|
||||
public addMembersTab = new AppAddMembersTab();
|
||||
public contactsTab = new AppContactsTab();
|
||||
public newGroupTab = new AppNewGroupTab();
|
||||
|
||||
private tabs: {[id: number]: SliderTab} = {
|
||||
[SLIDERITEMSIDS.newChannel]: this.newChannelTab,
|
||||
[SLIDERITEMSIDS.contacts]: this.contactsTab,
|
||||
[SLIDERITEMSIDS.addMembers]: this.addMembersTab,
|
||||
[SLIDERITEMSIDS.newGroup]: this.newGroupTab,
|
||||
};
|
||||
|
||||
//private log = logger('SL');
|
||||
|
||||
private searchGroups = {
|
||||
@ -42,12 +363,7 @@ class AppSidebarLeft {
|
||||
private globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups);
|
||||
|
||||
private _selectTab: (id: number) => void;
|
||||
private historyTabIDs: number[] = [];
|
||||
|
||||
private contactsList: HTMLUListElement;
|
||||
private contactsScrollable: Scrollable;
|
||||
private contactsPromise: Promise<void>;
|
||||
private contactsInput: HTMLInputElement;
|
||||
public historyTabIDs: number[] = [];
|
||||
|
||||
constructor() {
|
||||
let peopleContainer = document.createElement('div');
|
||||
@ -69,8 +385,7 @@ class AppSidebarLeft {
|
||||
});
|
||||
|
||||
this.contactsBtn.addEventListener('click', (e) => {
|
||||
this.openContacts();
|
||||
this.selectTab(SLIDERITEMSIDS.contacts);
|
||||
this.contactsTab.openContacts();
|
||||
});
|
||||
|
||||
this.logOutBtn.addEventListener('click', (e) => {
|
||||
@ -83,12 +398,6 @@ class AppSidebarLeft {
|
||||
this.searchContainer.classList.remove('hide');
|
||||
void this.searchContainer.offsetWidth; // reflow
|
||||
this.searchContainer.classList.add('active');
|
||||
|
||||
/* if(!this.globalSearch.searchInput.value) {
|
||||
for(let i in this.globalSearch.searchGroups) {
|
||||
this.globalSearch.searchGroups[i].clear();
|
||||
}
|
||||
} */
|
||||
|
||||
false && this.searchInput.addEventListener('blur', (e) => {
|
||||
if(!this.searchInput.value) {
|
||||
@ -96,10 +405,6 @@ class AppSidebarLeft {
|
||||
this.backBtn.classList.remove('active');
|
||||
this.backBtn.click();
|
||||
}
|
||||
|
||||
/* this.peerID = 0;
|
||||
this.loadedCount = 0;
|
||||
this.minMsgID = 0; */
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
@ -118,6 +423,18 @@ class AppSidebarLeft {
|
||||
}, 150);
|
||||
});
|
||||
|
||||
this.newButtons.channel.addEventListener('click', (e) => {
|
||||
this.selectTab(SLIDERITEMSIDS.newChannel);
|
||||
});
|
||||
|
||||
[this.newButtons.group, this.newGroupBtn].forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
this.addMembersTab.init(0, 'chat', false, (peerIDs) => {
|
||||
this.newGroupTab.init(peerIDs);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => {
|
||||
this.archivedCount.innerText = '' + e.detail.count;
|
||||
});
|
||||
@ -125,22 +442,14 @@ class AppSidebarLeft {
|
||||
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, 420);
|
||||
this._selectTab(0);
|
||||
|
||||
let onCloseBtnClick = () => {
|
||||
console.log('sidebar-close-button click:', this.historyTabIDs);
|
||||
let closingID = this.historyTabIDs.pop(); // pop current
|
||||
this.onCloseTab(closingID);
|
||||
this._selectTab(this.historyTabIDs.pop() || 0);
|
||||
};
|
||||
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
console.log('sidebar-close-button click:', this.historyTabIDs);
|
||||
let closingID = this.historyTabIDs.pop(); // pop current
|
||||
|
||||
// need to clear, and left 1 page for smooth slide
|
||||
if(closingID == SLIDERITEMSIDS.contacts) {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
(Array.from(this.contactsList.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove());
|
||||
setTimeout(() => {
|
||||
this.contactsList.innerHTML = '';
|
||||
}, 420);
|
||||
}
|
||||
|
||||
this._selectTab(this.historyTabIDs.pop() || 0);
|
||||
});
|
||||
el.addEventListener('click', onCloseBtnClick);
|
||||
});
|
||||
|
||||
appUsersManager.getTopPeers().then(categories => {
|
||||
@ -162,80 +471,34 @@ class AppSidebarLeft {
|
||||
this.searchGroups.people.setActive();
|
||||
});
|
||||
});
|
||||
|
||||
let contactsContainer = this.sidebarEl.querySelector('#contacts-container');
|
||||
this.contactsInput = contactsContainer.querySelector('#contacts-search');
|
||||
this.contactsList = contactsContainer.querySelector('#contacts') as HTMLUListElement;
|
||||
appDialogsManager.setListClickListener(this.contactsList);
|
||||
this.contactsScrollable = new Scrollable(this.contactsList.parentElement);
|
||||
|
||||
let prevValue = '';
|
||||
this.contactsInput.addEventListener('input', () => {
|
||||
let value = this.contactsInput.value;
|
||||
if(prevValue != value) {
|
||||
this.contactsList.innerHTML = '';
|
||||
this.openContacts(prevValue = value);
|
||||
}
|
||||
});
|
||||
|
||||
// preload contacts
|
||||
appUsersManager.getContacts();
|
||||
}
|
||||
|
||||
public openContacts(query?: string) {
|
||||
if(this.contactsPromise) return this.contactsPromise;
|
||||
this.contactsScrollable.onScrolledBottom = null;
|
||||
|
||||
this.contactsPromise = appUsersManager.getContacts(query).then(contacts => {
|
||||
this.contactsPromise = null;
|
||||
|
||||
if(this.historyTabIDs[this.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) {
|
||||
console.warn('user closed contacts before it\'s loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
let sorted = contacts
|
||||
.map(userID => {
|
||||
let user = appUsersManager.getUser(userID);
|
||||
let status = appUsersManager.getUserStatusForSort(user.status);
|
||||
|
||||
return {user, status};
|
||||
})
|
||||
.sort((a, b) => b.status - a.status);
|
||||
|
||||
let renderPage = () => {
|
||||
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
|
||||
let arr = sorted.splice(0, pageCount);
|
||||
|
||||
arr.forEach(({user}) => {
|
||||
let {dialog, dom} = appDialogsManager.addDialog(user.id, this.contactsList, false);
|
||||
|
||||
let status = appUsersManager.getUserStatusString(user.id);
|
||||
dom.lastMessageSpan.innerHTML = status == 'online' ? `<i>${status}</i>` : status;
|
||||
});
|
||||
|
||||
if(!sorted.length) renderPage = undefined;
|
||||
};
|
||||
|
||||
renderPage();
|
||||
this.contactsScrollable.onScrolledBottom = () => {
|
||||
if(renderPage) {
|
||||
renderPage();
|
||||
} else {
|
||||
this.contactsScrollable.onScrolledBottom = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public selectTab(id: number) {
|
||||
this.historyTabIDs.push(id);
|
||||
this._selectTab(id);
|
||||
}
|
||||
|
||||
public removeTabFromHistory(id: number) {
|
||||
this.historyTabIDs.findAndSplice(i => i == id);
|
||||
this.onCloseTab(id);
|
||||
}
|
||||
|
||||
public onCloseTab(id: number) {
|
||||
let tab = this.tabs[id];
|
||||
if(tab) {
|
||||
if('onClose' in tab) {
|
||||
tab.onClose();
|
||||
}
|
||||
|
||||
if('onCloseAfterTimeout' in tab) {
|
||||
setTimeout(() => {
|
||||
tab.onCloseAfterTimeout();
|
||||
}, 420);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const appSidebarLeft = new AppSidebarLeft();
|
||||
|
||||
(window as any).appSidebarLeft = appSidebarLeft;
|
||||
|
||||
export default appSidebarLeft;
|
||||
|
@ -158,12 +158,19 @@ export class AppUsersManager {
|
||||
contactsList.sort((userID1: number, userID2: number) => {
|
||||
const sortName1 = (this.users[userID1] || {}).sortName || '';
|
||||
const sortName2 = (this.users[userID2] || {}).sortName || '';
|
||||
|
||||
return sortName1.localeCompare(sortName2);
|
||||
});
|
||||
|
||||
/* contactsList.sort((userID1: number, userID2: number) => {
|
||||
const sortName1 = (this.users[userID1] || {}).sortName || '';
|
||||
const sortName2 = (this.users[userID2] || {}).sortName || '';
|
||||
if(sortName1 == sortName2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sortName1 > sortName2 ? 1 : -1;
|
||||
});
|
||||
}); */
|
||||
|
||||
return contactsList;
|
||||
});
|
||||
@ -215,7 +222,8 @@ export class AppUsersManager {
|
||||
this.usernames[searchUsername] = userID;
|
||||
}
|
||||
|
||||
apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''));
|
||||
//apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''));
|
||||
apiUser.sortName = apiUser.pFlags.deleted ? '' : apiUser.first_name + ' ' + (apiUser.last_name || '');
|
||||
|
||||
var nameWords = apiUser.sortName.split(' ');
|
||||
var firstWord = nameWords.shift();
|
||||
|
@ -345,17 +345,17 @@ export function longFromInts(high: number, low: number) {
|
||||
export function intToUint(val: number | string) {
|
||||
if(typeof(val) === 'string') val = parseInt(val);
|
||||
|
||||
if(val < 0) {
|
||||
/* if(val < 0) {
|
||||
val = val + 4294967296;
|
||||
}
|
||||
} */
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
export function uintToInt(val: number) {
|
||||
if(val > 2147483647) {
|
||||
/* if(val > 2147483647) {
|
||||
val = val - 4294967296;
|
||||
}
|
||||
} */
|
||||
|
||||
return val;
|
||||
}
|
||||
|
@ -157,7 +157,5 @@ class LottieLoader {
|
||||
}
|
||||
|
||||
const lottieLoader = new LottieLoader();
|
||||
|
||||
//(window as any).LottieLoader = lottieLoader;
|
||||
|
||||
(window as any).LottieLoader = lottieLoader;
|
||||
export default lottieLoader;
|
||||
|
@ -227,14 +227,14 @@ class MTPNetworker {
|
||||
if(!this.connectionInited) { // this will call once for each new session
|
||||
///////this.log('Wrap api call !this.connectionInited');
|
||||
|
||||
let invokeWithLayer = Schema.API.methods.find((m: any) => m.method == 'invokeWithLayer');
|
||||
let invokeWithLayer = Schema.API.methods.find(m => m.method == 'invokeWithLayer');
|
||||
if(!invokeWithLayer) throw new Error('no invokeWithLayer!');
|
||||
serializer.storeInt(+invokeWithLayer.id >>> 0, 'invokeWithLayer');
|
||||
|
||||
// @ts-ignore
|
||||
serializer.storeInt(Schema.layer, 'layer');
|
||||
|
||||
let initConnection = Schema.API.methods.find((m: any) => m.method == 'initConnection');
|
||||
let initConnection = Schema.API.methods.find(m => m.method == 'initConnection');
|
||||
if(!initConnection) throw new Error('no initConnection!');
|
||||
|
||||
serializer.storeInt(+initConnection.id >>> 0, 'initConnection');
|
||||
@ -260,7 +260,7 @@ class MTPNetworker {
|
||||
}
|
||||
|
||||
if(options.afterMessageID) {
|
||||
let invokeAfterMsg = Schema.API.methods.find((m: any) => m.method == 'invokeAfterMsg');
|
||||
let invokeAfterMsg = Schema.API.methods.find(m => m.method == 'invokeAfterMsg');
|
||||
if(!invokeAfterMsg) throw new Error('no invokeAfterMsg!');
|
||||
|
||||
this.log('Api call options.afterMessageID!');
|
||||
|
File diff suppressed because one or more lines are too long
@ -16,10 +16,10 @@ import {gzipUncompress} from '../crypto/crypto_utils';
|
||||
import {gzipUncompress} from '../bin_utils';
|
||||
/// #endif
|
||||
|
||||
const boolFalse = +Schema.API.constructors.find((c: any) => c.predicate == 'boolFalse').id >>> 0;
|
||||
const boolTrue = +Schema.API.constructors.find((c: any) => c.predicate == 'boolTrue').id >>> 0;
|
||||
const vector = +Schema.API.constructors.find((c: any) => c.predicate == 'vector').id >>> 0;
|
||||
const gzipPacked = +Schema.MTProto.constructors.find((c: any) => c.predicate == 'gzip_packed').id >>> 0;
|
||||
const boolFalse = +Schema.API.constructors.find(c => c.predicate == 'boolFalse').id >>> 0;
|
||||
const boolTrue = +Schema.API.constructors.find(c => c.predicate == 'boolTrue').id >>> 0;
|
||||
const vector = +Schema.API.constructors.find(c => c.predicate == 'vector').id >>> 0;
|
||||
const gzipPacked = +Schema.MTProto.constructors.find(c => c.predicate == 'gzip_packed').id >>> 0;
|
||||
|
||||
//console.log('boolFalse', boolFalse == 0xbc799737);
|
||||
|
||||
@ -628,7 +628,7 @@ class TLDeserialization {
|
||||
return result;
|
||||
}
|
||||
|
||||
var schema = (this.mtproto ? Schema.MTProto : Schema.API) as any;
|
||||
var schema = this.mtproto ? Schema.MTProto : Schema.API;
|
||||
var predicate = false;
|
||||
var constructorData: any = false;
|
||||
|
||||
@ -675,7 +675,7 @@ class TLDeserialization {
|
||||
}
|
||||
}
|
||||
|
||||
var i: number = index[constructorCmp];
|
||||
var i = index[constructorCmp];
|
||||
if(i) {
|
||||
constructorData = schema.constructors[i];
|
||||
}
|
||||
|
@ -367,7 +367,7 @@ export const langPack = {
|
||||
"messageActionChatJoined": "joined the group",
|
||||
"messageActionChatAddUser": "invited {user}",
|
||||
"messageActionChatAddUsers": "invited {} users",
|
||||
"messageActionChatLeave": "left group",
|
||||
"messageActionChatLeave": "left the group",
|
||||
"messageActionChatDeleteUser": "removed user",
|
||||
"messageActionChatJoinedByLink": "joined the group",
|
||||
"messageActionPinMessage": "pinned message",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { openBtnMenu, ripple } from "../components/misc";
|
||||
import { openBtnMenu/* , ripple */ } from "../components/misc";
|
||||
//import {stackBlurImage} from '../lib/StackBlur';
|
||||
import Page from "./page";
|
||||
|
||||
@ -44,7 +44,7 @@ let onFirstMount = () => import('../lib/appManagers/appImManager').then(() => {/
|
||||
|
||||
import('../lib/services');
|
||||
|
||||
(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
|
||||
//(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
|
||||
|
||||
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import {putPreloader} from '../components/misc';
|
||||
import resizeableImage from '../lib/cropper';
|
||||
import pageIm from './pageIm';
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import apiFileManager from '../lib/mtproto/apiFileManager';
|
||||
import Page from './page';
|
||||
import popupAvatar from '../components/popupAvatar';
|
||||
|
||||
let authCode: {
|
||||
'phone_number': string,
|
||||
@ -13,84 +12,13 @@ let authCode: {
|
||||
|
||||
let onFirstMount = () => {
|
||||
const pageElement = page.pageEl;
|
||||
const avatarInput = document.getElementById('avatar-input') as HTMLInputElement;
|
||||
const avatarPopup = document.getElementsByClassName('popup-avatar')[0];
|
||||
const avatarPreview = pageElement.querySelector('#canvas-avatar') as HTMLCanvasElement;
|
||||
const cropContainer = avatarPopup.getElementsByClassName('crop')[0] as HTMLDivElement;
|
||||
let avatarImage = new Image();
|
||||
cropContainer.append(avatarImage);
|
||||
|
||||
let avatarBlob: Blob;
|
||||
|
||||
(avatarPopup.getElementsByClassName('popup-close')[0] as HTMLButtonElement)
|
||||
.addEventListener('click', function(this, e) {
|
||||
/* let popup = findUpClassName(this, 'popup');
|
||||
popup.classList.remove('active'); */
|
||||
|
||||
setTimeout(() => {
|
||||
cropper.removeHandlers();
|
||||
if(avatarImage) {
|
||||
avatarImage.remove();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
/* e.cancelBubble = true;
|
||||
return false; */
|
||||
});
|
||||
|
||||
let cropper = {
|
||||
crop: () => {},
|
||||
removeHandlers: () => {}
|
||||
};
|
||||
|
||||
// apply
|
||||
avatarPopup.getElementsByClassName('btn-crop')[0].addEventListener('click', () => {
|
||||
cropper.crop();
|
||||
avatarPopup.classList.remove('active');
|
||||
cropper.removeHandlers();
|
||||
|
||||
avatarPreview.toBlob(blob => {
|
||||
avatarBlob = blob; // save blob to send after reg
|
||||
|
||||
// darken
|
||||
let ctx = avatarPreview.getContext('2d');
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
||||
ctx.fillRect(0, 0, avatarPreview.width, avatarPreview.height);
|
||||
}, 'image/jpeg', 1);
|
||||
|
||||
avatarImage.remove();
|
||||
});
|
||||
|
||||
avatarInput.addEventListener('change', (e: any) => {
|
||||
var file = e.target.files[0];
|
||||
if(!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
var contents = e.target.result as string;
|
||||
|
||||
avatarImage = new Image();
|
||||
cropContainer.append(avatarImage);
|
||||
avatarImage.src = contents;
|
||||
|
||||
avatarImage.onload = () => {
|
||||
/* let {w, h} = calcImageInBox(avatarImage.naturalWidth, avatarImage.naturalHeight, 460, 554);
|
||||
cropContainer.style.width = w + 'px';
|
||||
cropContainer.style.height = h + 'px'; */
|
||||
avatarPopup.classList.add('active');
|
||||
|
||||
cropper = resizeableImage(avatarImage, avatarPreview);
|
||||
avatarInput.value = '';
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}, false);
|
||||
|
||||
let uploadAvatar: () => Promise<any>;
|
||||
pageElement.querySelector('.auth-image').addEventListener('click', () => {
|
||||
avatarInput.click();
|
||||
popupAvatar.open(avatarPreview, (_uploadAvatar) => {
|
||||
uploadAvatar = _uploadAvatar;
|
||||
});
|
||||
});
|
||||
|
||||
const headerName = pageElement.getElementsByClassName('fullName')[0] as HTMLHeadingElement;
|
||||
@ -108,13 +36,13 @@ let onFirstMount = () => {
|
||||
};
|
||||
|
||||
let sendAvatar = () => new Promise((resolve, reject) => {
|
||||
if(!avatarBlob) {
|
||||
if(!uploadAvatar) {
|
||||
console.log('User has not selected avatar');
|
||||
return resolve();
|
||||
}
|
||||
|
||||
console.log('invoking uploadFile...');
|
||||
apiFileManager.uploadFile(avatarBlob).then((inputFile: any) => {
|
||||
uploadAvatar().then((inputFile: any) => {
|
||||
console.log('uploaded smthn', inputFile);
|
||||
|
||||
apiManager.invokeApi('photos.uploadProfilePhoto', {
|
||||
|
4
src/schema.json
Normal file
4
src/schema.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
src/schema_pretty.json
Normal file
1
src/schema_pretty.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1278
src/scss/partials/_chatBubble.scss
Normal file
1278
src/scss/partials/_chatBubble.scss
Normal file
File diff suppressed because it is too large
Load Diff
@ -176,12 +176,23 @@
|
||||
//vertical-align: unset;
|
||||
margin-top: -1.5px;
|
||||
}
|
||||
|
||||
&.is-verified:after {
|
||||
content: " ";
|
||||
background: url(/assets/img/icon-verified.svg);
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-last-message {
|
||||
img.emoji {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
span.emoji {
|
||||
@ -193,11 +204,11 @@
|
||||
}
|
||||
|
||||
.user-title, .user-last-message {
|
||||
max-width: 80%;
|
||||
max-width: 86%;
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
color: $darkblue;
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +218,7 @@
|
||||
margin-top: -.3rem;
|
||||
|
||||
&[class*=" tgico-"] {
|
||||
color: $success-color;
|
||||
color: $color-green;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@ -242,7 +253,7 @@
|
||||
}
|
||||
|
||||
.unread {
|
||||
background: $success-color;
|
||||
background: $color-green;
|
||||
}
|
||||
|
||||
.unread-muted, .tgico-pinnedchat {
|
||||
@ -269,6 +280,10 @@
|
||||
padding-bottom: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,6 +124,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-main .sidebar-content {
|
||||
.btn-menu {
|
||||
bottom: calc(100% + 10px);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.btn-corner {
|
||||
transform: translateY(0px);
|
||||
|
||||
&.menu-open:before {
|
||||
content: $tgico-close;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#search-container {
|
||||
@ -138,3 +154,33 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.new-channel-container, .new-group-container {
|
||||
.sidebar-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.avatar-edit {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 1px auto 32px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 380px;
|
||||
margin: 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.chats-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.caption {
|
||||
font-size: 14px;
|
||||
margin-top: 14px;
|
||||
margin-left: 23px;
|
||||
color: #707579;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, .88);
|
||||
/* color: $darkgrey; */
|
||||
/* color: $color-gray; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -122,7 +122,7 @@
|
||||
.media-viewer-caption {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: $darkgrey;
|
||||
color: $color-gray;
|
||||
transition: .2s;
|
||||
max-width: 50vw;
|
||||
word-break: break-word;
|
||||
|
@ -98,12 +98,12 @@
|
||||
|
||||
&-subtitle {
|
||||
text-align: center;
|
||||
color: $darkgrey;
|
||||
color: $color-gray;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
|
||||
&.online {
|
||||
color: $darkblue;
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@
|
||||
left: 24px;
|
||||
/* top: 0; */
|
||||
font-size: 24px;
|
||||
color: $darkgrey;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
p {
|
||||
@ -328,7 +328,7 @@
|
||||
font-size: 2rem;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
background-color: $blue;
|
||||
background-color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
// BROWSER SCROLL
|
||||
div.scrollable-y::-webkit-scrollbar {
|
||||
width: .375rem;
|
||||
height: 200px;
|
||||
//height: 200px;
|
||||
}
|
||||
|
||||
/* div.scrollable-y::-webkit-scrollbar-thumb {
|
||||
|
@ -1,5 +1,5 @@
|
||||
.menu-horizontal {
|
||||
color: $darkgrey;
|
||||
color: $color-gray;
|
||||
border-bottom: 1px solid $lightgrey;
|
||||
position: relative;
|
||||
|
||||
@ -25,13 +25,13 @@
|
||||
font-weight: 500;
|
||||
|
||||
&.active {
|
||||
color: $blue;
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&__stripe {
|
||||
position: absolute;
|
||||
background: $blue;
|
||||
background: $color-blue;
|
||||
//left: 0;
|
||||
left: -2px;
|
||||
transition: .3s transform, .3s width;
|
||||
|
@ -3,31 +3,20 @@ $border-radius: 8px;
|
||||
$border-radius-medium: 10px;
|
||||
$border-radius-big: 12px;
|
||||
$button-primary-background: #4EA4F6;
|
||||
$success-color: #4DCD5E;
|
||||
|
||||
$color-green: #4DCD5E;
|
||||
$color-error: #E53935;
|
||||
$color-gray: #707579;
|
||||
$color-blue: #50a2e9;
|
||||
|
||||
$lightblue: #e6ebee;
|
||||
$blue: #50a2e9;
|
||||
$darkblue: #50a2e9;
|
||||
|
||||
$lightgreen: #eeffde;
|
||||
$green: #4dcd5e;
|
||||
$darkgreen: #50af4f;
|
||||
|
||||
$dotgreen: #0ac630;
|
||||
$color-text-green: $darkgreen;
|
||||
|
||||
$lightgrey: #dadce0;
|
||||
$grey: #c4c9cc;
|
||||
$darkgrey: #707579;
|
||||
|
||||
$light: rgba($darkgrey, 0.08);
|
||||
|
||||
$text: #000000;
|
||||
$bg: #ffffff;
|
||||
$light: rgba($color-gray, 0.08);
|
||||
|
||||
$text-size: 16px;
|
||||
$time-size: 12px;
|
||||
|
||||
$large-screen: 1680px;
|
||||
//$large-screen: 16800px;
|
||||
@ -35,6 +24,7 @@ $large-screen: 1680px;
|
||||
@import "partials/ico";
|
||||
@import "partials/chatlist";
|
||||
@import "partials/chat";
|
||||
@import "partials/chatBubble";
|
||||
@import "partials/sidebar";
|
||||
@import "partials/leftSidebar";
|
||||
@import "partials/rightSidebar";
|
||||
@ -59,7 +49,7 @@ html {
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue;
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
button, input, optgroup, select, textarea, html {
|
||||
@ -163,7 +153,7 @@ input {
|
||||
justify-content: center;
|
||||
|
||||
&.active {
|
||||
color: $blue;
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@ -188,15 +178,6 @@ input {
|
||||
color: $color-error!important;
|
||||
}
|
||||
|
||||
.btn-menu-toggle {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
&.menu-open {
|
||||
background-color: rgba(112, 117, 121, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-menu {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
@ -294,7 +275,7 @@ input {
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
border-radius: 50%;
|
||||
background-color: $blue;
|
||||
background-color: $color-blue;
|
||||
text-align: center;
|
||||
font-size: 1.25em;
|
||||
/* overflow: hidden; */
|
||||
@ -412,7 +393,7 @@ input {
|
||||
height: 70px;
|
||||
|
||||
&-ico {
|
||||
background-color: $blue;
|
||||
background-color: $color-blue;
|
||||
border-radius: 5px;
|
||||
line-height: 10px;
|
||||
|
||||
@ -442,7 +423,7 @@ input {
|
||||
}
|
||||
|
||||
&-download {
|
||||
background-color: $blue;
|
||||
background-color: $color-blue;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@ -490,7 +471,7 @@ input {
|
||||
|
||||
&-size {
|
||||
white-space: nowrap;
|
||||
color: $darkgrey;
|
||||
color: $color-gray;
|
||||
font-size: 14px;
|
||||
padding-right: 32px;
|
||||
line-height: 1.3;
|
||||
@ -547,7 +528,7 @@ input {
|
||||
|
||||
&-toggle, &-download {
|
||||
border-radius: 50%;
|
||||
background-color: $blue;
|
||||
background-color: $color-blue;
|
||||
font-size: 2.3rem;
|
||||
align-items: center;
|
||||
}
|
||||
@ -566,7 +547,7 @@ input {
|
||||
fill: #CBCBCB;
|
||||
|
||||
&.active {
|
||||
fill: $blue;
|
||||
fill: $color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -670,32 +651,36 @@ input {
|
||||
}
|
||||
} */
|
||||
|
||||
.avatar-edit {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
&-canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $color-blue;
|
||||
}
|
||||
|
||||
.tgico-cameraadd {
|
||||
position: absolute;
|
||||
font-size: 48px;
|
||||
line-height: 48px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
z-index: 2;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.page-signUp {
|
||||
.auth-image {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -758,6 +743,10 @@ input {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
/* font-weight: 500; */
|
||||
|
||||
/* &:hover {
|
||||
border-color: #000;
|
||||
} */
|
||||
|
||||
&:focus {
|
||||
border-color: $button-primary-background;
|
||||
@ -951,16 +940,16 @@ input {
|
||||
}
|
||||
|
||||
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: $placeholder-color;
|
||||
color: #a2acb4;
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
|
||||
:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: $placeholder-color;
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: $placeholder-color;
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
input:focus, button:focus {
|
||||
@ -1019,7 +1008,7 @@ input:focus, button:focus {
|
||||
} */
|
||||
|
||||
.btn-primary {
|
||||
background: $blue;
|
||||
background: $color-blue;
|
||||
color: #fff;
|
||||
border-radius: $border-radius-medium;
|
||||
width: 100%;
|
||||
@ -1033,7 +1022,7 @@ input:focus, button:focus {
|
||||
padding: 0; // new
|
||||
|
||||
&:hover {
|
||||
background: darken($blue, 8%);
|
||||
background: darken($color-blue, 8%);
|
||||
}
|
||||
|
||||
svg, use {
|
||||
@ -1058,6 +1047,16 @@ input:focus, button:focus {
|
||||
}
|
||||
}
|
||||
|
||||
.btn-menu-toggle {
|
||||
position: relative;
|
||||
overflow: visible !important;
|
||||
font-weight: normal !important;
|
||||
|
||||
&:not(.btn-primary).menu-open {
|
||||
background-color: rgba(112, 117, 121, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.preloader {
|
||||
&-circular {
|
||||
animation: rotate 2s linear infinite;
|
||||
@ -1336,6 +1335,12 @@ img.emoji {
|
||||
display: flex;
|
||||
max-height: 100vh;
|
||||
min-height: 100vh;
|
||||
|
||||
.avatar-edit {
|
||||
.tgico-cameraadd {
|
||||
top: 52%;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
|
Loading…
x
Reference in New Issue
Block a user