Browse Source

Create group & channel; poll prepare

master
morethanwords 5 years ago
parent
commit
a884a9cea8
  1. 156
      src/components/appForward.ts
  2. 8
      src/components/appSearch.ts
  3. 214
      src/components/appSelectPeers.ts
  4. 200
      src/components/poll.ts
  5. 97
      src/components/popupAvatar.ts
  6. 24
      src/components/wrappers.ts
  7. 68
      src/format_schema.js
  8. 132
      src/lib/appManagers/appChatsManager.ts
  9. 19
      src/lib/appManagers/appDialogsManager.ts
  10. 114
      src/lib/appManagers/appImManager.ts
  11. 6
      src/lib/appManagers/appMessagesManager.ts
  12. 70
      src/lib/appManagers/appPeersManager.ts
  13. 89
      src/lib/appManagers/appPollsManager.ts
  14. 467
      src/lib/appManagers/appSidebarLeft.ts
  15. 12
      src/lib/appManagers/appUsersManager.ts
  16. 8
      src/lib/bin_utils.ts
  17. 4
      src/lib/lottieLoader.ts
  18. 6
      src/lib/mtproto/networker.ts
  19. 38
      src/lib/mtproto/schema.ts
  20. 12
      src/lib/mtproto/tl_utils.ts
  21. 2
      src/lib/utils.js
  22. 4
      src/pages/pageIm.ts
  23. 86
      src/pages/pageSignUp.ts
  24. 4
      src/schema.json
  25. 1
      src/schema.tl
  26. 1
      src/schema_pretty.json
  27. 1642
      src/scss/partials/_chat.scss
  28. 1278
      src/scss/partials/_chatBubble.scss
  29. 23
      src/scss/partials/_chatlist.scss
  30. 46
      src/scss/partials/_leftSidebar.scss
  31. 4
      src/scss/partials/_mediaViewer.scss
  32. 8
      src/scss/partials/_rightSIdebar.scss
  33. 2
      src/scss/partials/_scrollable.scss
  34. 6
      src/scss/partials/_slider.scss
  35. 125
      src/scss/style.scss

156
src/components/appForward.ts

@ -1,161 +1,7 @@ @@ -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;

8
src/components/appSearch.ts

@ -13,10 +13,10 @@ export class SearchGroup { @@ -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 { @@ -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

@ -0,0 +1,214 @@ @@ -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

@ -0,0 +1,200 @@ @@ -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

@ -0,0 +1,97 @@ @@ -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();

24
src/components/wrappers.ts

@ -15,6 +15,8 @@ import { CancellablePromise } from '../lib/polyfill'; @@ -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?: ( @@ -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) { @@ -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 @@ -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 @@ -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

@ -0,0 +1,68 @@ @@ -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' */));

132
src/lib/appManagers/appChatsManager.ts

@ -1,9 +1,43 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();

19
src/lib/appManagers/appDialogsManager.ts

@ -9,6 +9,7 @@ import { ripple, putPreloader } from "../../components/misc"; @@ -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 { @@ -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 { @@ -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';
}

114
src/lib/appManagers/appImManager.ts

@ -1,6 +1,6 @@ @@ -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"; @@ -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';
@ -839,7 +839,20 @@ export class AppImManager { @@ -839,7 +839,20 @@ export class AppImManager {
let onlines = chatOnlines ? chatOnlines.onlines : 1;
///////////this.log('chatInfo res:', chatInfo);
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);
if(chatInfo.pinned_msg_id) { // request pinned message
this.pinnedMsgID = chatInfo.pinned_msg_id;
@ -882,6 +895,9 @@ export class AppImManager { @@ -882,6 +895,9 @@ export class AppImManager {
}
}
}
} else {
this.subtitleEl.innerText = 'bot';
appSidebarRight.profileElements.subtitle.innerText = 'bot';
}
}
@ -1225,7 +1241,8 @@ export class AppImManager { @@ -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 { @@ -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 { @@ -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');
@ -1713,6 +1736,15 @@ export class AppImManager { @@ -1713,6 +1736,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');
messageDiv.innerHTML = 'unrecognized media type: ' + message.media._;
@ -1877,7 +1909,7 @@ export class AppImManager { @@ -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 { @@ -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,48 +2242,50 @@ export class AppImManager { @@ -2210,48 +2242,50 @@ export class AppImManager {
public handleUpdate(update: any) {
switch(update._) {
case 'updateUserTyping':
case 'updateChatUserTyping':
if(this.myID == update.user_id) {
return;
}
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;
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);
}
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;
}
//return;
}
appUsersManager.forceUserOnline(update.user_id);
appUsersManager.forceUserOnline(update.user_id);
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
let currentPeer = this.peerID == peerID;
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(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
else if(dialog) {
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
if(currentPeer) { // user
this.setPeerStatus();
if(currentPeer) { // user
this.setPeerStatus();
}
}
}
this.typingTimeouts[peerID] = setTimeout(() => {
this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id];
this.typingTimeouts[peerID] = setTimeout(() => {
this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id];
if(dialog) {
appDialogsManager.unsetTyping(dialog);
}
if(dialog) {
appDialogsManager.unsetTyping(dialog);
}
// лень просчитывать случаи
this.setPeerStatus();
}, 6000);
break;
// лень просчитывать случаи
this.setPeerStatus();
}, 6000);
break;
}
case 'updateNotifySettings': {
let {peer, notify_settings} = update;

6
src/lib/appManagers/appMessagesManager.ts

@ -21,6 +21,7 @@ import serverTimeManager from "../mtproto/serverTimeManager"; @@ -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 { @@ -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'};

70
src/lib/appManagers/appPeersManager.ts

@ -30,10 +30,12 @@ const AppPeersManager = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -125,25 +128,52 @@ const AppPeersManager = {
return (peerID > 0) && appUsersManager.isBot(peerID);
},
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
};
}
},
getInputPeerByID: (peerID: number) => {
if (!peerID) {
return {_: 'inputPeerEmpty'}
if(!peerID) {
return {_: 'inputPeerEmpty'};
}
if (peerID < 0) {
var chatID = -peerID
if (!appChatsManager.isChannel(chatID)) {
return {
_: 'inputPeerChat',
chat_id: chatID
};
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 = { @@ -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

@ -0,0 +1,89 @@ @@ -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();

467
src/lib/appManagers/appSidebarLeft.ts

@ -5,17 +5,318 @@ import appImManager from "./appImManager"; @@ -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 { @@ -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 { @@ -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 { @@ -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) => {
@ -84,22 +399,12 @@ class AppSidebarLeft { @@ -84,22 +399,12 @@ class AppSidebarLeft {
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) {
this.toolsBtn.classList.add('active');
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 { @@ -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 { @@ -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 { @@ -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;
public selectTab(id: number) {
this.historyTabIDs.push(id);
this._selectTab(id);
}
this.contactsPromise = appUsersManager.getContacts(query).then(contacts => {
this.contactsPromise = null;
public removeTabFromHistory(id: number) {
this.historyTabIDs.findAndSplice(i => i == id);
this.onCloseTab(id);
}
if(this.historyTabIDs[this.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) {
console.warn('user closed contacts before it\'s loaded');
return;
public onCloseTab(id: number) {
let tab = this.tabs[id];
if(tab) {
if('onClose' in tab) {
tab.onClose();
}
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);
if('onCloseAfterTimeout' in tab) {
setTimeout(() => {
tab.onCloseAfterTimeout();
}, 420);
}
}
}
}
const appSidebarLeft = new AppSidebarLeft();
(window as any).appSidebarLeft = appSidebarLeft;
export default appSidebarLeft;

12
src/lib/appManagers/appUsersManager.ts

@ -158,12 +158,19 @@ export class AppUsersManager { @@ -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 { @@ -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();

8
src/lib/bin_utils.ts

@ -345,17 +345,17 @@ export function longFromInts(high: number, low: number) { @@ -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;
}

4
src/lib/lottieLoader.ts

@ -157,7 +157,5 @@ class LottieLoader { @@ -157,7 +157,5 @@ class LottieLoader {
}
const lottieLoader = new LottieLoader();
//(window as any).LottieLoader = lottieLoader;
(window as any).LottieLoader = lottieLoader;
export default lottieLoader;

6
src/lib/mtproto/networker.ts

@ -227,14 +227,14 @@ class MTPNetworker { @@ -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 { @@ -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!');

38
src/lib/mtproto/schema.ts

File diff suppressed because one or more lines are too long

12
src/lib/mtproto/tl_utils.ts

@ -16,10 +16,10 @@ import {gzipUncompress} from '../crypto/crypto_utils'; @@ -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 { @@ -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 { @@ -675,7 +675,7 @@ class TLDeserialization {
}
}
var i: number = index[constructorCmp];
var i = index[constructorCmp];
if(i) {
constructorData = schema.constructors[i];
}

2
src/lib/utils.js

@ -367,7 +367,7 @@ export const langPack = { @@ -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",

4
src/pages/pageIm.ts

@ -1,4 +1,4 @@ @@ -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(() => {/ @@ -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) => {

86
src/pages/pageSignUp.ts

@ -1,10 +1,9 @@ @@ -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: { @@ -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 = () => { @@ -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

File diff suppressed because one or more lines are too long

1
src/schema.tl

File diff suppressed because one or more lines are too long

1
src/schema_pretty.json

File diff suppressed because one or more lines are too long

1642
src/scss/partials/_chat.scss

File diff suppressed because it is too large Load Diff

1278
src/scss/partials/_chatBubble.scss

File diff suppressed because it is too large Load Diff

23
src/scss/partials/_chatlist.scss

@ -176,12 +176,23 @@ @@ -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 @@ @@ -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 @@ @@ -207,7 +218,7 @@
margin-top: -.3rem;
&[class*=" tgico-"] {
color: $success-color;
color: $color-green;
font-size: 1.25rem;
}
@ -242,7 +253,7 @@ @@ -242,7 +253,7 @@
}
.unread {
background: $success-color;
background: $color-green;
}
.unread-muted, .tgico-pinnedchat {
@ -269,6 +280,10 @@ @@ -269,6 +280,10 @@
padding-bottom: 17px;
}
}
&:last-child {
border-bottom: none;
}
}
}

46
src/scss/partials/_leftSidebar.scss

@ -124,6 +124,22 @@ @@ -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 @@ @@ -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;
}
}

4
src/scss/partials/_mediaViewer.scss

@ -5,7 +5,7 @@ @@ -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 @@ @@ -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;

8
src/scss/partials/_rightSIdebar.scss

@ -98,12 +98,12 @@ @@ -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 @@ @@ -123,7 +123,7 @@
left: 24px;
/* top: 0; */
font-size: 24px;
color: $darkgrey;
color: $color-gray;
}
p {
@ -328,7 +328,7 @@ @@ -328,7 +328,7 @@
font-size: 2rem;
color: #fff;
text-transform: uppercase;
background-color: $blue;
background-color: $color-blue;
}
}

2
src/scss/partials/_scrollable.scss

@ -100,7 +100,7 @@ div.scrollable::-webkit-scrollbar-thumb { @@ -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 {

6
src/scss/partials/_slider.scss

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
.menu-horizontal {
color: $darkgrey;
color: $color-gray;
border-bottom: 1px solid $lightgrey;
position: relative;
@ -25,13 +25,13 @@ @@ -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;

125
src/scss/style.scss

@ -3,31 +3,20 @@ $border-radius: 8px; @@ -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; @@ -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 { @@ -59,7 +49,7 @@ html {
}
a {
color: $blue;
color: $color-blue;
}
button, input, optgroup, select, textarea, html {
@ -163,7 +153,7 @@ input { @@ -163,7 +153,7 @@ input {
justify-content: center;
&.active {
color: $blue;
color: $color-blue;
}
&:hover {
@ -188,15 +178,6 @@ input { @@ -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 { @@ -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 { @@ -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 { @@ -442,7 +423,7 @@ input {
}
&-download {
background-color: $blue;
background-color: $color-blue;
border-radius: 8px;
}
@ -490,7 +471,7 @@ input { @@ -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 { @@ -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 { @@ -566,7 +547,7 @@ input {
fill: #CBCBCB;
&.active {
fill: $blue;
fill: $color-blue;
}
}
}
@ -670,32 +651,36 @@ input { @@ -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;
}
}
}
@ -759,6 +744,10 @@ input { @@ -759,6 +744,10 @@ input {
z-index: 1;
/* font-weight: 500; */
/* &:hover {
border-color: #000;
} */
&:focus {
border-color: $button-primary-background;
border-width: 2px;
@ -951,16 +940,16 @@ input { @@ -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 { @@ -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 { @@ -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 { @@ -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;
@ -1337,6 +1336,12 @@ img.emoji { @@ -1337,6 +1336,12 @@ img.emoji {
max-height: 100vh;
min-height: 100vh;
.avatar-edit {
.tgico-cameraadd {
top: 52%;
}
}
> div {
height: 100%;
position: relative;

Loading…
Cancel
Save