fixes and fixes

This commit is contained in:
morethanwords 2020-06-20 04:11:24 +03:00
parent b2ef3c8bda
commit 257d334e77
43 changed files with 928 additions and 953 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ __pycache__
dist dist
.DS_Store .DS_Store
stats.json stats.json
certs certs
src/rlottie.github.io

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -50,9 +50,10 @@ export class SearchGroup {
export default class AppSearch { export default class AppSearch {
private minMsgID = 0; private minMsgID = 0;
private loadedCount = 0; private loadedCount = -1;
private foundCount = 0; private foundCount = -1;
private offsetRate = 0; private offsetRate = 0;
private loadedContacts = false;
private searchPromise: Promise<void> = null; private searchPromise: Promise<void> = null;
private searchTimeout: number = 0; private searchTimeout: number = 0;
@ -107,9 +108,10 @@ export default class AppSearch {
} }
this.minMsgID = 0; this.minMsgID = 0;
this.loadedCount = 0; this.loadedCount = -1;
this.foundCount = 0; this.foundCount = -1;
this.offsetRate = 0; this.offsetRate = 0;
this.loadedContacts = false;
for(let i in this.searchGroups) { for(let i in this.searchGroups) {
this.searchGroups[i].clear(); this.searchGroups[i].clear();
@ -129,25 +131,27 @@ export default class AppSearch {
public searchMore() { public searchMore() {
if(this.searchPromise) return this.searchPromise; if(this.searchPromise) return this.searchPromise;
let query = this.query; const query = this.query;
if(!query.trim()) { if(!query.trim()) {
this.onSearch && this.onSearch(0); this.onSearch && this.onSearch(0);
return; return;
} }
if(this.loadedCount != 0 && this.loadedCount >= this.foundCount) { if(this.foundCount != -1 && this.loadedCount >= this.foundCount) {
return Promise.resolve(); return Promise.resolve();
} }
let maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0]; const maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0] || 0;
if(!this.peerID && !maxID) { if(!this.peerID && !maxID && !this.loadedContacts) {
appUsersManager.searchContacts(query, 20).then((contacts: any) => { appUsersManager.searchContacts(query, 20).then((contacts: any) => {
if(this.searchInput.value != query) { if(this.searchInput.value != query) {
return; return;
} }
this.loadedContacts = true;
///////this.log('input search contacts result:', contacts); ///////this.log('input search contacts result:', contacts);
let setResults = (results: any, group: SearchGroup, showMembersCount = false) => { let setResults = (results: any, group: SearchGroup, showMembersCount = false) => {
@ -205,19 +209,19 @@ export default class AppSearch {
return; return;
} }
//console.log('input search result:', this.peerID, query, null, maxID, 20, res); console.log('input search result:', this.peerID, query, null, maxID, 20, res);
let {count, history, next_rate} = res; const {count, history, next_rate} = res;
if(history[0] == this.minMsgID) { if(history[0] == this.minMsgID) {
history.shift(); history.shift();
} }
let searchGroup = this.searchGroups['messages']; const searchGroup = this.searchGroups['messages'];
searchGroup.setActive(); searchGroup.setActive();
history.forEach((msgID: number) => { history.forEach((msgID: number) => {
let message = appMessagesManager.getMessage(msgID); const message = appMessagesManager.getMessage(msgID);
let originalDialog = appMessagesManager.getDialogByPeerID(message.peerID)[0]; let originalDialog = appMessagesManager.getDialogByPeerID(message.peerID)[0];
if(!originalDialog) { if(!originalDialog) {
@ -230,15 +234,18 @@ export default class AppSearch {
} as any; } as any;
} }
let {dialog, dom} = appDialogsManager.addDialog(originalDialog, this.scrollable/* searchGroup.list */, false); const {dialog, dom} = appDialogsManager.addDialog(originalDialog, this.scrollable/* searchGroup.list */, false);
appDialogsManager.setLastMessage(dialog, message, dom, query); appDialogsManager.setLastMessage(dialog, message, dom, query);
}); });
this.minMsgID = history[history.length - 1]; this.minMsgID = history[history.length - 1];
this.offsetRate = next_rate; this.offsetRate = next_rate;
this.loadedCount += history.length; this.loadedCount += history.length;
if(this.loadedCount == -1) {
this.loadedCount = 0;
}
if(!this.foundCount) { if(this.foundCount == -1) {
this.foundCount = count; this.foundCount = count;
this.onSearch && this.onSearch(this.foundCount); this.onSearch && this.onSearch(this.foundCount);
} }

View File

@ -208,7 +208,7 @@ export class AppSelectPeers {
} }
public add(peerID: any, title?: string) { public add(peerID: any, title?: string) {
console.trace('add'); //console.trace('add');
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('selector-user', 'scale-in'); div.classList.add('selector-user', 'scale-in');

View File

@ -271,6 +271,7 @@ function wrapAudio(doc: MTDocument, audioEl: AudioElement) {
export default class AudioElement extends HTMLElement { export default class AudioElement extends HTMLElement {
public audio: HTMLAudioElement; public audio: HTMLAudioElement;
public preloader: ProgressivePreloader;
private attachedHandlers: {[name: string]: any[]} = {}; private attachedHandlers: {[name: string]: any[]} = {};
private onTypeDisconnect: () => void; private onTypeDisconnect: () => void;
@ -303,7 +304,7 @@ export default class AudioElement extends HTMLElement {
const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement; const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement;
audioTimeDiv.innerHTML = durationStr; audioTimeDiv.innerHTML = durationStr;
let preloader: ProgressivePreloader; let preloader: ProgressivePreloader = this.preloader;
let promise: CancellablePromise<Blob>; let promise: CancellablePromise<Blob>;
const onLoad = () => { const onLoad = () => {
@ -377,7 +378,8 @@ export default class AudioElement extends HTMLElement {
this.addEventListener('click', onClick); this.addEventListener('click', onClick);
this.click(); this.click();
} else { } else {
onLoad(); this.preloader.attach(this.querySelector('.audio-download'), false);
//onLoad();
} }
} }
@ -402,6 +404,8 @@ export default class AudioElement extends HTMLElement {
delete this.attachedHandlers[name]; delete this.attachedHandlers[name];
} }
this.preloader = null;
} }
static get observedAttributes(): string[] { static get observedAttributes(): string[] {

View File

@ -151,7 +151,7 @@ export class ChatInput {
}).then((webpage: any) => { }).then((webpage: any) => {
appWebPagesManager.saveWebPage(webpage); appWebPagesManager.saveWebPage(webpage);
if(this.lastUrl != url) return; if(this.lastUrl != url) return;
console.log('got webpage: ', webpage); //console.log('got webpage: ', webpage);
this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url); this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url);
@ -235,7 +235,7 @@ export class ChatInput {
return new Promise<HTMLDivElement>((resolve, reject) => { return new Promise<HTMLDivElement>((resolve, reject) => {
let params: SendFileParams = {}; let params: SendFileParams = {};
params.file = file; params.file = file;
console.log('selected file:', file, typeof(file), willAttach); //console.log('selected file:', file, typeof(file), willAttach);
let itemDiv = document.createElement('div'); let itemDiv = document.createElement('div');
switch(willAttach.type) { switch(willAttach.type) {
case 'media': { case 'media': {
@ -249,6 +249,8 @@ export class ChatInput {
source.src = params.objectURL = URL.createObjectURL(file); source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false; video.autoplay = false;
video.controls = false; video.controls = false;
video.muted = true;
video.setAttribute('playsinline', '');
video.onloadeddata = () => { video.onloadeddata = () => {
params.width = video.videoWidth; params.width = video.videoWidth;
@ -283,6 +285,8 @@ export class ChatInput {
type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc' type: file.type.indexOf('image/') !== -1 ? 'photo' : 'doc'
} as any, false, true); } as any, false, true);
params.objectURL = URL.createObjectURL(file);
itemDiv.append(docDiv); itemDiv.append(docDiv);
resolve(itemDiv); resolve(itemDiv);
break; break;
@ -364,7 +368,7 @@ export class ChatInput {
this.attachMediaPopUp.mediaContainer.append(div); this.attachMediaPopUp.mediaContainer.append(div);
} }
console.log('chatInput album layout:', layout); //console.log('chatInput album layout:', layout);
} else { } else {
let params = willAttach.sendFileDetails[0]; let params = willAttach.sendFileDetails[0];
let div = results[0]; let div = results[0];
@ -407,11 +411,13 @@ export class ChatInput {
}, false); }, false);
this.attachMenu.media.addEventListener('click', () => { this.attachMenu.media.addEventListener('click', () => {
this.fileInput.setAttribute('accept', 'image/*, video/*');
willAttach.type = 'media'; willAttach.type = 'media';
this.fileInput.click(); this.fileInput.click();
}); });
this.attachMenu.document.addEventListener('click', () => { this.attachMenu.document.addEventListener('click', () => {
this.fileInput.removeAttribute('accept');
willAttach.type = 'document'; willAttach.type = 'document';
this.fileInput.click(); this.fileInput.click();
}); });
@ -451,7 +457,7 @@ export class ChatInput {
let caption = this.attachMediaPopUp.captionInput.value; let caption = this.attachMediaPopUp.captionInput.value;
willAttach.isMedia = willAttach.type == 'media'; willAttach.isMedia = willAttach.type == 'media';
console.log('will send files with options:', willAttach); //console.log('will send files with options:', willAttach);
let peerID = appImManager.peerID; let peerID = appImManager.peerID;
@ -471,7 +477,8 @@ export class ChatInput {
let promises = willAttach.sendFileDetails.map(params => { let promises = willAttach.sendFileDetails.map(params => {
let promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({ let promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
isMedia: willAttach.isMedia, //isMedia: willAttach.isMedia,
isMedia: true,
caption, caption,
replyToMsgID: this.replyToMsgID replyToMsgID: this.replyToMsgID
}, params)); }, params));

View File

@ -3,7 +3,7 @@ import { horizontalMenu, renderImageFromUrl, putPreloader } from "./misc";
import lottieLoader from "../lib/lottieLoader"; import lottieLoader from "../lib/lottieLoader";
//import Scrollable from "./scrollable"; //import Scrollable from "./scrollable";
import Scrollable from "./scrollable_new"; import Scrollable from "./scrollable_new";
import { findUpTag, whichChild, calcImageInBox } from "../lib/utils"; import { findUpTag, whichChild, calcImageInBox, emojiUnicode } from "../lib/utils";
import { RichTextProcessor } from "../lib/richtextprocessor"; import { RichTextProcessor } from "../lib/richtextprocessor";
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager"; import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
//import apiManager from '../lib/mtproto/apiManager'; //import apiManager from '../lib/mtproto/apiManager';
@ -16,6 +16,7 @@ import Config, { touchSupport } from "../lib/config";
import { MTDocument } from "../types"; import { MTDocument } from "../types";
import animationIntersector from "./animationIntersector"; import animationIntersector from "./animationIntersector";
import appSidebarRight from "../lib/appManagers/appSidebarRight"; import appSidebarRight from "../lib/appManagers/appSidebarRight";
import appStateManager from "../lib/appManagers/appStateManager";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -27,6 +28,12 @@ interface EmoticonsTab {
class EmojiTab implements EmoticonsTab { class EmojiTab implements EmoticonsTab {
public content: HTMLElement; public content: HTMLElement;
private recent: string[] = [];
private recentItemsDiv: HTMLElement;
private heights: number[] = [];
private scroll: Scrollable;
init() { init() {
this.content = document.getElementById('content-emoji') as HTMLDivElement; this.content = document.getElementById('content-emoji') as HTMLDivElement;
@ -37,7 +44,9 @@ class EmojiTab implements EmoticonsTab {
const sorted: { const sorted: {
[category: string]: string[] [category: string]: string[]
} = {}; } = {
'Recent': []
};
for(const emoji in Config.Emoji) { for(const emoji in Config.Emoji) {
const details = Config.Emoji[emoji]; const details = Config.Emoji[emoji];
@ -72,46 +81,48 @@ class EmojiTab implements EmoticonsTab {
const emojis = sorted[category]; const emojis = sorted[category];
emojis.forEach(emoji => { emojis.forEach(emoji => {
//const emoji = details.unified; /* if(emojiUnicode(emoji) == '1f481-200d-2642') {
//const emoji = (details.unified as string).split('-') console.log('append emoji', emoji, emojiUnicode(emoji));
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); } */
const spanEmoji = document.createElement('span'); this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv);
const kek = RichTextProcessor.wrapRichText(emoji);
if(!kek.includes('emoji')) { /* if(category == 'Smileys & Emotion') {
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji)); console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
return; } */
}
//console.log(kek);
spanEmoji.innerHTML = kek;
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
itemsDiv.appendChild(spanEmoji);
}); });
divs[category] = div; divs[category] = div;
} }
//console.timeEnd('emojiParse'); //console.timeEnd('emojiParse');
const heights: number[] = [0]; let prevCategoryIndex = 0;
let prevCategoryIndex = 1;
const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement; const menu = this.content.previousElementSibling.firstElementChild as HTMLUListElement;
const emojiScroll = new Scrollable(this.content, 'y', 'EMOJI', null); const emojiScroll = this.scroll = new Scrollable(this.content, 'y', 'EMOJI', null);
emojiScroll.container.addEventListener('scroll', (e) => { emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container);
}); });
//emojiScroll.setVirtualContainer(emojiScroll.container); //emojiScroll.setVirtualContainer(emojiScroll.container);
const preloader = putPreloader(this.content, true); const preloader = putPreloader(this.content, true);
setTimeout(() => { Promise.all([
new Promise((resolve) => setTimeout(resolve, 200)),
appStateManager.getState().then(state => {
if(Array.isArray(state.recentEmoji)) {
this.recent = state.recentEmoji;
}
})
]).then(() => {
preloader.remove(); preloader.remove();
this.recentItemsDiv = divs['Recent'].querySelector('.category-items');
for(const emoji of this.recent) {
this.appendEmoji(emoji, this.recentItemsDiv);
}
categories.unshift('Recent');
categories.map(category => { categories.map(category => {
const div = divs[category]; const div = divs[category];
@ -123,27 +134,86 @@ class EmojiTab implements EmoticonsTab {
return div; return div;
}).forEach(div => { }).forEach(div => {
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight); //console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
heights.push((heights[heights.length - 1] || 0) + div.scrollHeight); this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
}); });
}, 200); });
this.content.addEventListener('click', this.onContentClick); this.content.addEventListener('click', this.onContentClick);
EmoticonsDropdown.menuOnClick(menu, heights, emojiScroll); EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll);
this.init = null; this.init = null;
} }
private appendEmoji(emoji: string, container: HTMLElement, prepend = false) {
//const emoji = details.unified;
//const emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
const spanEmoji = document.createElement('span');
const kek = RichTextProcessor.wrapEmojiText(emoji);
/* if(!kek.includes('emoji')) {
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
return;
} */
//console.log(kek);
spanEmoji.innerHTML = kek;
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
if(prepend) container.prepend(spanEmoji);
else container.appendChild(spanEmoji);
}
private getEmojiFromElement(element: HTMLElement) {
if(element.tagName == 'SPAN' && !element.classList.contains('emoji')) {
element = element.firstElementChild as HTMLElement;
}
return element.getAttribute('alt') || element.innerText;
}
onContentClick = (e: MouseEvent) => { onContentClick = (e: MouseEvent) => {
let target = e.target as any; let target = e.target as HTMLElement;
//if(target.tagName != 'SPAN') return; //if(target.tagName != 'SPAN') return;
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) { if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
target = target.firstElementChild; target = target.firstElementChild as HTMLElement;
} else if(target.tagName == 'DIV') return; } else if(target.tagName == 'DIV') return;
//console.log('contentEmoji div', target); //console.log('contentEmoji div', target);
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML; appImManager.chatInputC.messageInput.innerHTML += target.outerHTML;
// Recent
const emoji = this.getEmojiFromElement(target);
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
const _emoji = this.getEmojiFromElement(el);
if(emoji == _emoji) {
el.remove();
}
});
const scrollHeight = this.recentItemsDiv.scrollHeight;
this.appendEmoji(emoji, this.recentItemsDiv, true);
// нужно поставить новые размеры для скролла
if(this.recentItemsDiv.scrollHeight != scrollHeight) {
this.heights.length = 0;
(Array.from(this.scroll.container.children) as HTMLElement[]).forEach(div => {
this.heights.push((this.heights[this.heights.length - 1] || 0) + div.scrollHeight);
});
}
this.recent.findAndSplice(e => e == emoji);
this.recent.unshift(emoji);
if(this.recent.length > 36) {
this.recent.length = 36;
}
appStateManager.pushToState('recentEmoji', this.recent);
// Append to input
const event = new Event('input', {bubbles: true, cancelable: true}); const event = new Event('input', {bubbles: true, cancelable: true});
appImManager.chatInputC.messageInput.dispatchEvent(event); appImManager.chatInputC.messageInput.dispatchEvent(event);
}; };
@ -685,6 +755,7 @@ class EmoticonsDropdown {
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) { if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
/* dropdown.classList.remove('active'); /* dropdown.classList.remove('active');
toggleEl.classList.remove('active'); */ toggleEl.classList.remove('active'); */
emoticonsDropdown.toggle(false);
} else { } else {
console.warn('got no doc by id:', fileID); console.warn('got no doc by id:', fileID);
} }

View File

@ -111,8 +111,10 @@ export default class ProgressivePreloader {
return; return;
} }
let totalLength = this.circle.getTotalLength(); try {
//console.log('setProgress', (percents / 100 * totalLength)); let totalLength = this.circle.getTotalLength();
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200'; //console.log('setProgress', (percents / 100 * totalLength));
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200';
} catch(err) {}
} }
} }

View File

@ -143,6 +143,8 @@ export default class AppChatFoldersTab implements SliderTab {
delete this.filtersRendered[filter.id] delete this.filtersRendered[filter.id]
} }
}); });
this.getSuggestedFilters();
} }
private getSuggestedFilters() { private getSuggestedFilters() {

View File

@ -31,7 +31,10 @@ export default class AppSettingsTab implements SliderTab {
}); });
this.logOutBtn.addEventListener('click', (e) => { this.logOutBtn.addEventListener('click', (e) => {
apiManager.logOut(); apiManager.logOut().finally(() => {
localStorage.clear();
location.reload();
});
}); });
this.buttons.edit.addEventListener('click', () => { this.buttons.edit.addEventListener('click', () => {

File diff suppressed because one or more lines are too long

View File

@ -90,7 +90,7 @@ if(false) {
formatted.forEach(e => { formatted.forEach(e => {
let {unified, name, short_names, category, sheet_x, sheet_y, sort_order} = e; let {unified, name, short_names, category, sheet_x, sheet_y, sort_order} = e;
let emoji = unified.replace(/-FE0F/gi, '').split('-') let emoji = unified/* .replace(/-FE0F/gi, '') */.split('-')
.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); .reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let c = categories[category] === undefined ? 9 : categories[category]; let c = categories[category] === undefined ? 9 : categories[category];

View File

@ -77,7 +77,7 @@
<p class="subtitle sent-type"></p> <p class="subtitle sent-type"></p>
<div class="input-wrapper"> <div class="input-wrapper">
<div class="input-field"> <div class="input-field">
<input type="number" name="code" id="code" autocomplete="off" required /> <input type="tel" name="code" id="code" autocomplete="off" required />
<label for="code">Code</label> <label for="code">Code</label>
</div> </div>
</div> </div>
@ -214,7 +214,7 @@
<div class="folders-tabs-scrollable"> <div class="folders-tabs-scrollable">
<nav class="menu-horizontal" style="display: none;" id="folders-tabs"> <nav class="menu-horizontal" style="display: none;" id="folders-tabs">
<ul> <ul>
<li class="rp"><span>All</span></li> <li class="rp"><span>All</span><span class="unread-count"></span></li>
</ul> </ul>
</nav> </nav>
</div> </div>
@ -268,12 +268,12 @@
</div> </div>
<div class="input-wrapper"> <div class="input-wrapper">
<div class="input-field"> <div class="input-field">
<input type="text" name="name" class="new-channel-name" autocomplete="xxDDqqOX" required=""> <input type="text" name="name" class="new-channel-name" id="new-channel-name" autocomplete="xxDDqqOX" required="">
<label for="name">Channel Name</label> <label for="new-channel-name">Channel Name</label>
</div> </div>
<div class="input-field"> <div class="input-field">
<input type="text" name="description" class="new-channel-description" autocomplete="aintsofunnow" required=""> <input type="text" name="description" class="new-channel-description" id="new-channel-description" autocomplete="aintsofunnow" required="">
<label for="lastName">Description (optional)</label> <label for="new-channel-description">Description (optional)</label>
</div> </div>
</div> </div>
<div class="caption">You can provide an optional description for your channel.</div> <div class="caption">You can provide an optional description for your channel.</div>
@ -301,8 +301,8 @@
</div> </div>
<div class="input-wrapper"> <div class="input-wrapper">
<div class="input-field"> <div class="input-field">
<input type="text" name="name" class="new-group-name" autocomplete="feellikeamonster2112" required=""> <input type="text" name="name" class="new-group-name" id="new-group-name" autocomplete="feellikeamonster2112" required="">
<label for="name">Group Name</label> <label for="new-group-name">Group Name</label>
</div> </div>
</div> </div>
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-next"></button> <button class="btn-primary btn-circle btn-icon rp btn-corner tgico-next"></button>

View File

@ -1,272 +0,0 @@
/*
* Copyright (c) 2018-present, Evgeny Nadymov
*
* This source code is licensed under the GPL v.3.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import MP4Box from 'mp4box/dist/mp4box.all.min';
//import { LOG, logSourceBufferRanges } from '../Utils/Common';
let LOG = () => console.log(...arguments);
export default class MP4Source {
constructor(video, getBufferAsync) {
this.mp4file = null;
this.nextBufferStart = 0;
this.mediaSource = null;
this.ready = false;
this.bufferedTime = 40;
this.beforeMoovBufferSize = 32 * 1024;
this.moovBufferSize = 512 * 1024;
this.bufferSize = 1024 * 1024;
this.seekBufferSize = 1024 * 1024;
this.currentBufferSize = this.beforeMoovBufferSize;
this.nbSamples = 10;
this.video = video;
this.getBufferAsync = getBufferAsync;
this.expectedSize = this.video.video.expected_size;
this.seeking = false;
this.loading = false;
this.url = null;
this.init(video.duration);
}
init(videoDuration) {
const mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', async () => {
LOG('[MediaSource] sourceopen start', this.mediaSource, this);
if (this.mediaSource.sourceBuffers.length > 0) return;
const mp4File = MP4Box.createFile();
mp4File.onMoovStart = () => {
LOG('[MP4Box] onMoovStart');
this.currentBufferSize = this.moovBufferSize;
};
mp4File.onError = error => {
LOG('[MP4Box] onError', error);
};
mp4File.onReady = info => {
LOG('[MP4Box] onReady', info);
this.ready = true;
this.currentBufferSize = this.bufferSize;
const { isFragmented, timescale, fragment_duration, duration } = info;
if (!fragment_duration && !duration) {
this.mediaSource.duration = videoDuration;
this.bufferedTime = videoDuration;
} else {
this.mediaSource.duration = isFragmented
? fragment_duration / timescale
: duration / timescale;
}
for (let i = 0; i < info.tracks.length; i++) {
this.addSourceBuffer(mp4File, this.mediaSource, info.tracks[i]);
}
const initSegs = mp4File.initializeSegmentation();
LOG('[MP4Box] initializeSegmentation', initSegs);
for (let i = 0; i < initSegs.length; i++) {
const { user: sourceBuffer } = initSegs[i];
sourceBuffer.onupdateend = () => {
sourceBuffer.initSegs = true;
sourceBuffer.onupdateend = this.handleSourceBufferUpdateEnd;
};
sourceBuffer.appendBuffer(initSegs[i].buffer);
}
LOG('[MP4Box] start fragmentation');
mp4File.start();
};
mp4File.onSegment = (id, sourceBuffer, buffer, sampleNum, is_last) => {
const isLast = (sampleNum + this.nbSamples) > sourceBuffer.nb_samples;
LOG('[MP4Box] onSegment', id, buffer, `${sampleNum}/${sourceBuffer.nb_samples}`, isLast, sourceBuffer.timestampOffset);
if (mediaSource.readyState !== 'open') {
return;
}
sourceBuffer.pendingUpdates.push({ id, buffer, sampleNum, isLast });
if (sourceBuffer.initSegs && !sourceBuffer.updating) {
this.handleSourceBufferUpdateEnd({ target: sourceBuffer, mediaSource: this.mediaSource });
}
};
this.nextBufferStart = 0;
this.mp4file = mp4File;
LOG('[MediaSource] sourceopen end', this, this.mp4file);
this.loadNextBuffer();
});
mediaSource.addEventListener('sourceended', () => {
LOG('[MediaSource] sourceended', mediaSource.readyState);
});
mediaSource.addEventListener('sourceclose', () => {
LOG('[MediaSource] sourceclose', mediaSource.readyState);
});
this.mediaSource = mediaSource;
}
addSourceBuffer(file, source, track) {
if (!track) return null;
const { id, codec, type: trackType, nb_samples } = track;
const type = `video/mp4; codecs="${codec}"`;
if (!MediaSource.isTypeSupported(type)) {
LOG('[addSourceBuffer] not supported', type);
return null;
}
// if (trackType !== 'video') {
// LOG('[addSourceBuffer] skip', trackType);
// return null;
// }
const sourceBuffer = source.addSourceBuffer(type);
sourceBuffer.id = id;
sourceBuffer.pendingUpdates = [];
sourceBuffer.nb_samples = nb_samples;
file.setSegmentOptions(id, sourceBuffer, { nbSamples: this.nbSamples });
LOG('[addSourceBuffer] add', id, codec, trackType);
return sourceBuffer;
}
handleSourceBufferUpdateEnd = event => {
const { target: sourceBuffer } = event;
const { mediaSource, mp4file } = this;
if (!sourceBuffer) return;
if (sourceBuffer.updating) return;
//logSourceBufferRanges(sourceBuffer, 0, 0);
const { pendingUpdates } = sourceBuffer;
if (!pendingUpdates) return;
if (!pendingUpdates.length) {
if (sourceBuffer.isLast && mediaSource.readyState === 'open') {
LOG('[SourceBuffer] updateend endOfStream start', sourceBuffer.id);
if (Array.from(mediaSource.sourceBuffers).every(x => !x.pendingUpdates.length && !x.updating)) {
mediaSource.endOfStream();
LOG('[SourceBuffer] updateend endOfStream stop', sourceBuffer.id);
}
}
return;
}
const update = pendingUpdates.shift();
if (!update) return;
const { id, buffer, sampleNum, isLast } = update;
if (sampleNum) {
LOG('[SourceBuffer] updateend releaseUsedSamples', id, sampleNum);
mp4file.releaseUsedSamples(id, sampleNum);
}
LOG('[SourceBuffer] updateend end', sourceBuffer.id, sourceBuffer.pendingUpdates.length);
sourceBuffer.isLast = isLast;
sourceBuffer.appendBuffer(buffer);
};
getURL() {
this.url = this.url || URL.createObjectURL(this.mediaSource);
return this.url;
}
seek(currentTime, buffered) {
const seekInfo = this.mp4file.seek(currentTime, true);
this.nextBufferStart = seekInfo.offset;
let loadNextBuffer = buffered.length === 0;
for (let i = 0; i < buffered.length; i++) {
const start = buffered.start(i);
const end = buffered.end(i);
if (start <= currentTime && currentTime + this.bufferedTime > end) {
loadNextBuffer = true;
break;
}
}
LOG('[player] onSeeked', loadNextBuffer, currentTime, seekInfo, this.nextBufferStart);
if (loadNextBuffer) {
this.loadNextBuffer(true);
}
}
timeUpdate(currentTime, duration, buffered) {
const ranges = [];
for (let i = 0; i < buffered.length; i++) {
ranges.push({ start: buffered.start(i), end: buffered.end(i)})
}
let loadNextBuffer = buffered.length === 0;
let hasRange = false;
for (let i = 0; i < buffered.length; i++) {
const start = buffered.start(i);
const end = buffered.end(i);
if (start <= currentTime && currentTime <= end) {
hasRange = true;
if (end < duration && currentTime + this.bufferedTime > end) {
loadNextBuffer = true;
break;
}
}
}
if (!hasRange) {
loadNextBuffer = true;
}
LOG('[player] timeUpdate', loadNextBuffer, currentTime, duration, JSON.stringify(ranges));
if (loadNextBuffer) {
this.loadNextBuffer();
}
}
async loadNextBuffer(seek = false) {
const { nextBufferStart, loading, currentBufferSize, mp4file } = this;
LOG('[player] loadNextBuffer', nextBufferStart === undefined, loading, !mp4file);
if (!mp4file) return;
if (nextBufferStart === undefined) return;
if (loading) return;
this.loading = true;
let bufferSize = seek ? this.seekBufferSize : this.bufferSize;
if (nextBufferStart + bufferSize > this.expectedSize) {
bufferSize = this.expectedSize - nextBufferStart;
}
const nextBuffer = await this.getBufferAsync(nextBufferStart, nextBufferStart + bufferSize);
nextBuffer.fileStart = nextBufferStart;
LOG('[player] loadNextBuffer start', nextBuffer.byteLength, nextBufferStart);
if (nextBuffer.byteLength) {
this.nextBufferStart = mp4file.appendBuffer(nextBuffer);
} else {
this.nextBufferStart = undefined;
}
LOG('[player] loadNextBuffer stop', nextBuffer.byteLength, nextBufferStart, this.nextBufferStart);
if (nextBuffer.byteLength < currentBufferSize) {
LOG('[player] loadNextBuffer flush');
this.mp4file.flush();
}
this.loading = false;
if (!this.ready) {
LOG('[player] loadNextBuffer next');
this.loadNextBuffer();
}
}
}

View File

@ -29,7 +29,7 @@ export default class MP4Source {
private loading = false; private loading = false;
private url: string; private url: string;
private log = logger('MP4', LogLevels.error); private log = logger('MP4'/* , LogLevels.error */);
//public onLoadBuffer: (offset: number) //public onLoadBuffer: (offset: number)

View File

@ -1,279 +0,0 @@
/*
* Copyright (c) 2018-present, Evgeny Nadymov
*
* This source code is licensed under the GPL v.3.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
// @ts-ignore
import MP4Box from 'mp4box/dist/mp4box.all.min';
let LOG = (...args: any[]) => {
console.log(...args);
};
export default class MP4Source {
private mp4file: any;
private nextBufferStart = 0;
private mediaSource: MediaSource = null;
private ready = false;
private bufferedTime = 40;
private beforeMoovBufferSize = 32 * 1024;
private moovBufferSize = 512 * 1024;
private bufferSize = 512 * 1024;
private seekBufferSize = 512 * 1024;
private currentBufferSize = this.beforeMoovBufferSize;
private nbSamples = 12;
private expectedSize: number;
private seeking = false;
private loading = false;
private url: string = null;
constructor(private video: {duration: number, video: {expected_size: number}}, private getBufferAsync: (start: number, end: number) => Promise<ArrayBuffer>) {
this.expectedSize = this.video.video.expected_size;
this.init(video.duration);
}
init(videoDuration: any) {
const mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', async () => {
LOG('[MediaSource] sourceopen start', this.mediaSource, this);
if (this.mediaSource.sourceBuffers.length > 0) return;
const mp4File = MP4Box.createFile();
mp4File.onMoovStart = () => {
LOG('[MP4Box] onMoovStart');
this.currentBufferSize = this.moovBufferSize;
};
mp4File.onError = (error: any) => {
LOG('[MP4Box] onError', error);
};
mp4File.onReady = (info: any) => {
LOG('[MP4Box] onReady', info);
this.ready = true;
this.currentBufferSize = this.bufferSize;
const { isFragmented, timescale, fragment_duration, duration } = info;
if (!fragment_duration && !duration) {
this.mediaSource.duration = videoDuration;
this.bufferedTime = videoDuration;
} else {
this.mediaSource.duration = isFragmented
? fragment_duration / timescale
: duration / timescale;
}
for (let i = 0; i < info.tracks.length; i++) {
this.addSourceBuffer(mp4File, this.mediaSource, info.tracks[i]);
}
const initSegs = mp4File.initializeSegmentation();
LOG('[MP4Box] initializeSegmentation', initSegs);
for (let i = 0; i < initSegs.length; i++) {
const { user: sourceBuffer } = initSegs[i];
sourceBuffer.onupdateend = () => {
sourceBuffer.initSegs = true;
sourceBuffer.onupdateend = this.handleSourceBufferUpdateEnd;
};
sourceBuffer.appendBuffer(initSegs[i].buffer);
}
LOG('[MP4Box] start fragmentation');
mp4File.start();
setInterval(() => {
this.loadNextBuffer();
}, 1e3);
};
mp4File.onSegment = (id: any, sourceBuffer: any, buffer: any, sampleNum: any, is_last: boolean) => {
const isLast = (sampleNum + this.nbSamples) > sourceBuffer.nb_samples;
LOG('[MP4Box] onSegment', id, buffer, `${sampleNum}/${sourceBuffer.nb_samples}`, isLast, sourceBuffer.timestampOffset);
if (mediaSource.readyState !== 'open') {
return;
}
sourceBuffer.pendingUpdates.push({ id, buffer, sampleNum, isLast });
if (sourceBuffer.initSegs && !sourceBuffer.updating) {
this.handleSourceBufferUpdateEnd({ target: sourceBuffer, mediaSource: this.mediaSource });
}
};
this.nextBufferStart = 0;
this.mp4file = mp4File;
LOG('[MediaSource] sourceopen end', this, this.mp4file);
this.loadNextBuffer();
});
mediaSource.addEventListener('sourceended', () => {
LOG('[MediaSource] sourceended', mediaSource.readyState);
});
mediaSource.addEventListener('sourceclose', () => {
LOG('[MediaSource] sourceclose', mediaSource.readyState);
});
this.mediaSource = mediaSource;
}
addSourceBuffer(file: any, source: any, track: any) {
if (!track) return null;
const { id, codec, type: trackType, nb_samples } = track;
const type = `video/mp4; codecs="${codec}"`;
if (!MediaSource.isTypeSupported(type)) {
LOG('[addSourceBuffer] not supported', type);
return null;
}
// if (trackType !== 'video') {
// LOG('[addSourceBuffer] skip', trackType);
// return null;
// }
const sourceBuffer = source.addSourceBuffer(type);
sourceBuffer.id = id;
sourceBuffer.pendingUpdates = [];
sourceBuffer.nb_samples = nb_samples;
file.setSegmentOptions(id, sourceBuffer, { nbSamples: this.nbSamples });
LOG('[addSourceBuffer] add', id, codec, trackType);
return sourceBuffer;
}
handleSourceBufferUpdateEnd = (event: any) => {
const { target: sourceBuffer } = event;
const { mediaSource, mp4file } = this;
if (!sourceBuffer) return;
if (sourceBuffer.updating) return;
//logSourceBufferRanges(sourceBuffer, 0, 0);
const { pendingUpdates } = sourceBuffer;
if (!pendingUpdates) return;
if (!pendingUpdates.length) {
if (sourceBuffer.isLast && mediaSource.readyState === 'open') {
LOG('[SourceBuffer] updateend endOfStream start', sourceBuffer.id);
if (Array.from(mediaSource.sourceBuffers).every((x: any) => !x.pendingUpdates.length && !x.updating)) {
mediaSource.endOfStream();
LOG('[SourceBuffer] updateend endOfStream stop', sourceBuffer.id);
}
}
return;
}
const update = pendingUpdates.shift();
if (!update) return;
const { id, buffer, sampleNum, isLast } = update;
if (sampleNum) {
LOG('[SourceBuffer] updateend releaseUsedSamples', id, sampleNum);
mp4file.releaseUsedSamples(id, sampleNum);
}
LOG('[SourceBuffer] updateend end', sourceBuffer.id, sourceBuffer.pendingUpdates.length);
sourceBuffer.isLast = isLast;
sourceBuffer.appendBuffer(buffer);
};
getURL() {
this.url = this.url || URL.createObjectURL(this.mediaSource);
return this.url;
}
seek(currentTime: number, buffered: any) {
const seekInfo = this.mp4file.seek(currentTime, true);
this.nextBufferStart = seekInfo.offset;
let loadNextBuffer = buffered.length === 0;
for (let i = 0; i < buffered.length; i++) {
const start = buffered.start(i);
const end = buffered.end(i);
if (start <= currentTime && currentTime + this.bufferedTime > end) {
loadNextBuffer = true;
break;
}
}
LOG('[player] onSeeked', loadNextBuffer, currentTime, seekInfo, this.nextBufferStart);
if (loadNextBuffer) {
this.loadNextBuffer(true);
}
}
timeUpdate(currentTime: number, duration: number, buffered: any) {
const ranges = [];
for (let i = 0; i < buffered.length; i++) {
ranges.push({ start: buffered.start(i), end: buffered.end(i)})
}
let loadNextBuffer = buffered.length === 0;
let hasRange = false;
for (let i = 0; i < buffered.length; i++) {
const start = buffered.start(i);
const end = buffered.end(i);
if (start <= currentTime && currentTime <= end) {
hasRange = true;
if (end < duration && currentTime + this.bufferedTime > end) {
loadNextBuffer = true;
break;
}
}
}
if (!hasRange) {
loadNextBuffer = true;
}
LOG('[player] timeUpdate', loadNextBuffer, currentTime, duration, JSON.stringify(ranges));
if (loadNextBuffer) {
this.loadNextBuffer();
}
}
async loadNextBuffer(seek = false) {
const { nextBufferStart, loading, currentBufferSize, mp4file } = this;
LOG('[player] loadNextBuffer', nextBufferStart === undefined, loading, !mp4file);
if (!mp4file) return;
if (nextBufferStart === undefined) return;
if (loading) return;
this.loading = true;
let bufferSize = seek ? this.seekBufferSize : this.bufferSize;
if (nextBufferStart + bufferSize > this.expectedSize) {
bufferSize = this.expectedSize - nextBufferStart;
}
const nextBuffer = await this.getBufferAsync(nextBufferStart, nextBufferStart + bufferSize);
// @ts-ignore
nextBuffer.fileStart = nextBufferStart;
LOG('[player] loadNextBuffer start', nextBuffer.byteLength, nextBufferStart);
if (nextBuffer.byteLength) {
this.nextBufferStart = mp4file.appendBuffer(nextBuffer);
} else {
this.nextBufferStart = undefined;
}
LOG('[player] loadNextBuffer stop', nextBuffer.byteLength, nextBufferStart, this.nextBufferStart);
if (nextBuffer.byteLength < currentBufferSize) {
LOG('[player] loadNextBuffer flush');
this.mp4file.flush();
}
this.loading = false;
if (!this.ready) {
LOG('[player] loadNextBuffer next');
this.loadNextBuffer();
}
}
}

View File

@ -12,6 +12,7 @@ import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar"; import AvatarElement from "../../components/avatar";
import { PopupButton, PopupPeer } from "../../components/popup"; import { PopupButton, PopupPeer } from "../../components/popup";
import { SliderTab } from "../../components/slider"; import { SliderTab } from "../../components/slider";
import appStateManager from "./appStateManager";
type DialogDom = { type DialogDom = {
avatarEl: AvatarElement, avatarEl: AvatarElement,
@ -330,7 +331,7 @@ export class AppDialogsManager {
public chatsContainer = document.getElementById('chats-container') as HTMLDivElement; public chatsContainer = document.getElementById('chats-container') as HTMLDivElement;
private chatsPreloader: HTMLDivElement; private chatsPreloader: HTMLDivElement;
public loadDialogsPromise: ReturnType<AppMessagesManager["getConversations"]>; public loadDialogsPromise: Promise<any>;
public loadedAll = false; public loadedAll = false;
public scroll: Scrollable = null; public scroll: Scrollable = null;
@ -351,15 +352,21 @@ export class AppDialogsManager {
}; };
private filtersRendered: { private filtersRendered: {
[filterID: string]: { [filterID: string]: {
menu: HTMLElement, container: HTMLElement menu: HTMLElement,
container: HTMLElement,
unread: HTMLElement
} }
} = {}; } = {};
private showFiltersTimeout: number;
private allUnreadCount: HTMLElement;
private accumulateArchivedTimeout: number; private accumulateArchivedTimeout: number;
constructor() { constructor() {
this.chatList.addEventListener('contextmenu', this.contextMenu.onContextMenu); this.chatList.addEventListener('contextmenu', this.contextMenu.onContextMenu);
this.chatsPreloader = putPreloader(null, true); this.chatsPreloader = putPreloader(null, true);
this.allUnreadCount = this.folders.menu.querySelector('.unread-count');
if(USEPINNEDDELIMITER) { if(USEPINNEDDELIMITER) {
this.pinnedDelimiter = document.createElement('div'); this.pinnedDelimiter = document.createElement('div');
@ -423,6 +430,7 @@ export class AppDialogsManager {
this.setDialogPosition(dialog); this.setDialogPosition(dialog);
this.setPinnedDelimiter(); this.setPinnedDelimiter();
this.setFiltersUnreadCount();
}); });
$rootScope.$on('dialog_flush', (e: CustomEvent) => { $rootScope.$on('dialog_flush', (e: CustomEvent) => {
@ -431,6 +439,7 @@ export class AppDialogsManager {
if(dialog) { if(dialog) {
this.setLastMessage(dialog); this.setLastMessage(dialog);
this.validateForFilter(); this.validateForFilter();
this.setFiltersUnreadCount();
} }
}); });
@ -444,6 +453,7 @@ export class AppDialogsManager {
this.setPinnedDelimiter(); this.setPinnedDelimiter();
this.validateForFilter(); this.validateForFilter();
this.setFiltersUnreadCount();
}); });
$rootScope.$on('dialog_drop', (e: CustomEvent) => { $rootScope.$on('dialog_drop', (e: CustomEvent) => {
@ -455,6 +465,8 @@ export class AppDialogsManager {
delete this.doms[peerID]; delete this.doms[peerID];
this.scroll.reorder(); this.scroll.reorder();
} }
this.setFiltersUnreadCount();
}); });
$rootScope.$on('dialog_unread', (e: CustomEvent) => { $rootScope.$on('dialog_unread', (e: CustomEvent) => {
@ -472,6 +484,7 @@ export class AppDialogsManager {
} }
this.validateForFilter(); this.validateForFilter();
this.setFiltersUnreadCount();
} }
}); });
@ -505,6 +518,7 @@ export class AppDialogsManager {
const dialog = folder[i]; const dialog = folder[i];
this.updateDialog(dialog); this.updateDialog(dialog);
} }
this.setFiltersUnreadCount();
} }
}); });
@ -549,6 +563,7 @@ export class AppDialogsManager {
if(this.filterID == id) return; if(this.filterID == id) return;
this.chatLists[id].innerHTML = '';
this.scroll.setVirtualContainer(this.chatLists[id]); this.scroll.setVirtualContainer(this.chatLists[id]);
this.filterID = id; this.filterID = id;
this.onTabChange(); this.onTabChange();
@ -563,7 +578,7 @@ export class AppDialogsManager {
//selectTab(0); //selectTab(0);
(this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click(); (this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click();
/* false && */appMessagesManager.loadSavedState().then(() => { /* false && */appStateManager.loadSavedState().then(() => {
return appMessagesManager.filtersStorage.getDialogFilters(); return appMessagesManager.filtersStorage.getDialogFilters();
}).then(filters => { }).then(filters => {
for(const filterID in filters) { for(const filterID in filters) {
@ -601,6 +616,26 @@ export class AppDialogsManager {
this.loadDialogs(this.filterID); this.loadDialogs(this.filterID);
}; };
public setFilterUnreadCount(filterID: number, folder?: Dialog[]) {
const unreadSpan = filterID == 0 ? this.allUnreadCount : this.filtersRendered[filterID]?.unread;
if(!unreadSpan) {
return;
}
folder = folder || appMessagesManager.dialogsStorage.getFolder(filterID);
const sum = folder.reduce((acc, dialog) => acc + +!!dialog.unread_count, 0);
unreadSpan.innerText = sum ? '' + sum : '';
}
public setFiltersUnreadCount() {
for(const filterID in this.filtersRendered) {
this.setFilterUnreadCount(+filterID);
}
this.setFilterUnreadCount(0);
}
/** /**
* Удалит неподходящие чаты из списка, но не добавит их(!) * Удалит неподходящие чаты из списка, но не добавит их(!)
*/ */
@ -635,7 +670,9 @@ export class AppDialogsManager {
const li = document.createElement('li'); const li = document.createElement('li');
const span = document.createElement('span'); const span = document.createElement('span');
span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title);
li.append(span); const unreadSpan = document.createElement('span');
unreadSpan.classList.add('unread-count');
li.append(span, unreadSpan);
ripple(li); ripple(li);
this.folders.menu.firstElementChild.append(li); this.folders.menu.firstElementChild.append(li);
@ -650,13 +687,18 @@ export class AppDialogsManager {
this.setListClickListener(ul); this.setListClickListener(ul);
ul.addEventListener('contextmenu', this.contextMenu.onContextMenu); ul.addEventListener('contextmenu', this.contextMenu.onContextMenu);
setTimeout(() => { if(!this.showFiltersTimeout) {
this.folders.menu.style.display = ''; this.showFiltersTimeout = setTimeout(() => {
}, 0); this.showFiltersTimeout = 0;
this.folders.menu.style.display = '';
this.setFiltersUnreadCount();
}, 0);
}
this.filtersRendered[filter.id] = { this.filtersRendered[filter.id] = {
menu: li, menu: li,
container: div container: div,
unread: unreadSpan
}; };
} }
@ -688,9 +730,14 @@ export class AppDialogsManager {
//console.time('getDialogs time'); //console.time('getDialogs time');
const loadCount = 50/*this.chatsLoadCount */; const loadCount = 50/*this.chatsLoadCount */;
this.loadDialogsPromise = appMessagesManager.getConversations('', offsetIndex, loadCount, folderID);
const getConversationPromise = (this.filterID > 1 ? appUsersManager.getContacts() as Promise<any> : Promise.resolve()).then(() => {
return appMessagesManager.getConversations('', offsetIndex, loadCount, folderID);
});
this.loadDialogsPromise = getConversationPromise;
const result = await this.loadDialogsPromise; const result = await getConversationPromise;
//console.timeEnd('getDialogs time'); //console.timeEnd('getDialogs time');
@ -1170,6 +1217,11 @@ export class AppDialogsManager {
this.doms[dialog.peerID] = dom; this.doms[dialog.peerID] = dom;
if($rootScope.selectedPeerID == peerID) {
li.classList.add('active');
this.lastActiveListElement = li;
}
/* if(container) { /* if(container) {
container.append(li); container.append(li);
} */ } */

View File

@ -294,7 +294,11 @@ class AppDocsManager {
}; };
promise.then(() => { promise.then(() => {
deferred.resolve(this.createMP4Stream(doc)); if(doc.url) { // может быть уже загружен из кэша
deferred.resolve();
} else {
deferred.resolve(this.createMP4Stream(doc));
}
}); });
return deferred; return deferred;

View File

@ -678,6 +678,12 @@ export class AppImManager {
} }
} }
if(['audio', 'voice'].includes(message.media?.document?.type)) {
const audio = bubble.querySelector('audio-element');
audio.setAttribute('doc-id', message.media.document.id);
audio.setAttribute('message-id', '' + mid);
}
bubble.classList.remove('is-sending'); bubble.classList.remove('is-sending');
bubble.classList.add('is-sent'); bubble.classList.add('is-sent');
bubble.dataset.mid = mid; bubble.dataset.mid = mid;
@ -1404,12 +1410,12 @@ export class AppImManager {
if(samePeer) { if(samePeer) {
if(this.bubbles[lastMsgID]) { if(this.bubbles[lastMsgID]) {
if(dialog && lastMsgID == topMessage) { if(isTarget) {
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight;
} else if(isTarget) {
this.scrollable.scrollIntoView(this.bubbles[lastMsgID]); this.scrollable.scrollIntoView(this.bubbles[lastMsgID]);
this.highlightBubble(this.bubbles[lastMsgID]); this.highlightBubble(this.bubbles[lastMsgID]);
} else if(dialog && lastMsgID == topMessage) {
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight;
} }
return true; return true;
@ -1503,14 +1509,15 @@ export class AppImManager {
this.lazyLoadQueue.unlock(); this.lazyLoadQueue.unlock();
if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { //if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
if(dialog && (isTarget || (lastMsgID != topMessage)) && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
if(this.scrollable.scrollLocked) { if(this.scrollable.scrollLocked) {
clearTimeout(this.scrollable.scrollLocked); clearTimeout(this.scrollable.scrollLocked);
this.scrollable.scrollLocked = 0; this.scrollable.scrollLocked = 0;
} }
const fromUp = maxBubbleID > 0 && (maxBubbleID < lastMsgID || lastMsgID < 0); const fromUp = maxBubbleID > 0 && (maxBubbleID < lastMsgID || lastMsgID < 0);
const forwardingUnread = dialog.read_inbox_max_id == lastMsgID; const forwardingUnread = dialog.read_inbox_max_id == lastMsgID && !isTarget;
if(!fromUp && (samePeer || forwardingUnread)) { if(!fromUp && (samePeer || forwardingUnread)) {
this.scrollable.scrollTop = this.scrollable.scrollHeight; this.scrollable.scrollTop = this.scrollable.scrollHeight;
} }
@ -2209,9 +2216,14 @@ export class AppImManager {
let doc = appDocsManager.getDoc(message.id); let doc = appDocsManager.getDoc(message.id);
this.log('will wrap pending doc:', doc); this.log('will wrap pending doc:', doc);
let docDiv = wrapDocument(doc, false, true, message.id); let docDiv = wrapDocument(doc, false, true, message.id);
let icoDiv = docDiv.querySelector('.audio-download, .document-ico'); if(doc.type == 'audio' || doc.type == 'voice') {
preloader.attach(icoDiv, false); // @ts-ignore
docDiv.preloader = preloader;
} else {
let icoDiv = docDiv.querySelector('.audio-download, .document-ico');
preloader.attach(icoDiv, false);
}
if(pending.type == 'voice') { if(pending.type == 'voice') {
bubble.classList.add('bubble-audio'); bubble.classList.add('bubble-audio');
@ -2735,7 +2747,10 @@ export class AppImManager {
promise = result.then((result) => { promise = result.then((result) => {
this.log('getHistory not cached result by maxID:', maxID, reverse, isBackLimit, result, peerID); this.log('getHistory not cached result by maxID:', maxID, reverse, isBackLimit, result, peerID);
if(justLoad) return true; if(justLoad) {
this.scrollable.onScroll(); // нужно делать из-за ранней прогрузки
return true;
}
//console.timeEnd('appImManager call getHistory'); //console.timeEnd('appImManager call getHistory');
if(this.peerID != peerID) { if(this.peerID != peerID) {

View File

@ -475,13 +475,14 @@ export class AppMediaViewer {
} }
private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) { private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
let media = aspecter.firstElementChild; /* let media = aspecter.firstElementChild;
let proportion: number; let proportion: number;
if(media instanceof HTMLImageElement) { if(media instanceof HTMLImageElement) {
proportion = media.naturalWidth / media.naturalHeight; proportion = media.naturalWidth / media.naturalHeight;
} else if(media instanceof HTMLVideoElement) { } else if(media instanceof HTMLVideoElement) {
proportion = media.videoWidth / media.videoHeight; proportion = media.videoWidth / media.videoHeight;
} } */
const proportion = containerRect.width / containerRect.height;
let {width, height} = rect; let {width, height} = rect;
/* if(proportion == 1) { /* if(proportion == 1) {

View File

@ -59,7 +59,6 @@ export type Dialog = {
index: number, index: number,
peerID: number, peerID: number,
pinnedIndex: number,
pFlags: Partial<{ pFlags: Partial<{
pinned: true, pinned: true,
unread_mark: true unread_mark: true
@ -67,15 +66,15 @@ export type Dialog = {
pts: number pts: number
} }
class DialogsStorage { export class DialogsStorage {
public dialogs: {[peerID: string]: Dialog} = {}; public dialogs: {[peerID: string]: Dialog} = {};
public byFolders: {[folderID: number]: Dialog[]} = {}; public byFolders: {[folderID: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {}; public allDialogsLoaded: {[folder_id: number]: boolean} = {};
public dialogsOffsetDate: {[folder_id: number]: number} = {}; public dialogsOffsetDate: {[folder_id: number]: number} = {};
public pinnedIndexes: {[folder_id: number]: number} = { public pinnedOrders: {[folder_id: number]: number[]} = {
0: 0, 0: [],
1: 0 1: []
}; };
public dialogsNum = 0; public dialogsNum = 0;
@ -176,26 +175,14 @@ class DialogsStorage {
} }
public generateDialogPinnedDateByIndex(pinnedIndex: number) { public generateDialogPinnedDateByIndex(pinnedIndex: number) {
return 0x7fffff00 + (pinnedIndex & 0xff); return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов
} }
public generateDialogPinnedDate(dialog?: Dialog) { public generateDialogPinnedDate(dialog: Dialog) {
const folderID = dialog.folder_id; const order = this.pinnedOrders[dialog.folder_id];
let pinnedIndex: number;
if(dialog) {
if(dialog.hasOwnProperty('pinnedIndex')) {
pinnedIndex = dialog.pinnedIndex;
} else {
dialog.pinnedIndex = pinnedIndex = this.pinnedIndexes[folderID]++;
}
} else {
pinnedIndex = this.pinnedIndexes[folderID]++;
}
if(pinnedIndex > this.pinnedIndexes[folderID]) { const foundIndex = order.indexOf(dialog.peerID);
this.pinnedIndexes[folderID] = pinnedIndex; const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex;
}
return this.generateDialogPinnedDateByIndex(pinnedIndex); return this.generateDialogPinnedDateByIndex(pinnedIndex);
} }
@ -268,7 +255,7 @@ export type DialogFilter = {
include_peers: number[], include_peers: number[],
exclude_peers: number[] exclude_peers: number[]
}; };
class FiltersStorage { export class FiltersStorage {
public filters: {[filterID: string]: DialogFilter} = {}; public filters: {[filterID: string]: DialogFilter} = {};
constructor() { constructor() {
@ -465,11 +452,18 @@ class FiltersStorage {
} }
public getOutputDialogFilter(filter: DialogFilter) { public getOutputDialogFilter(filter: DialogFilter) {
const c = copy(filter); const c: DialogFilter = copy(filter);
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => { ['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
c[key] = c[key].map((peerID: number) => appPeersManager.getInputPeerByID(peerID)); c[key] = c[key].map((peerID: number) => appPeersManager.getInputPeerByID(peerID));
}); });
c.include_peers.forEachReverse((peerID, idx) => {
if(c.pinned_peers.includes(peerID)) {
c.include_peers.splice(idx, 1);
}
});
return c; return c;
} }
@ -500,6 +494,14 @@ class FiltersStorage {
filter[key] = filter[key].map((peer: any) => appPeersManager.getPeerID(peer)); filter[key] = filter[key].map((peer: any) => appPeersManager.getPeerID(peer));
}); });
filter.include_peers.forEachReverse((peerID, idx) => {
if(filter.pinned_peers.includes(peerID)) {
filter.include_peers.splice(idx, 1);
}
});
filter.include_peers = filter.pinned_peers.concat(filter.include_peers);
/* if(this.filters[filter.id]) { /* if(this.filters[filter.id]) {
// ну давай же найдём различия теперь, раз они сами не хотят приходить // ну давай же найдём различия теперь, раз они сами не хотят приходить
const oldFilter = this.filters[filter.id]; const oldFilter = this.filters[filter.id];
@ -578,7 +580,8 @@ export class AppMessagesManager {
public newDialogsToHandle: {[peerID: string]: {reload: true} | Dialog} = {}; public newDialogsToHandle: {[peerID: string]: {reload: true} | Dialog} = {};
public newUpdatesAfterReloadToHandle: any = {}; public newUpdatesAfterReloadToHandle: any = {};
public loaded: Promise<any> = null; private reloadConversationsPromise: Promise<void>;
private reloadConversationsPeers: number[] = [];
private dialogsIndex = searchIndexManager.createIndex(); private dialogsIndex = searchIndexManager.createIndex();
private cachedResults: { private cachedResults: {
@ -652,156 +655,7 @@ export class AppMessagesManager {
}); });
} }
public loadSavedState() {
if(this.loaded) return this.loaded;
return this.loaded = new Promise((resolve, reject) => {
AppStorage.get<{
dialogs: Dialog[],
allDialogsLoaded: DialogsStorage['allDialogsLoaded'],
peers: any[],
messages: any[],
contactsList: number[],
updates: any,
filters: FiltersStorage['filters'],
maxSeenMsgID: number
}>('state').then(({dialogs, allDialogsLoaded, peers, messages, contactsList, maxSeenMsgID, updates, filters}) => {
this.log('state res', dialogs, messages);
if(maxSeenMsgID && !appMessagesIDsManager.getMessageIDInfo(maxSeenMsgID)[1]) {
this.maxSeenID = maxSeenMsgID;
}
//return resolve();
if(peers) {
for(let peerID in peers) {
let peer = peers[peerID];
if(+peerID < 0) appChatsManager.saveApiChat(peer);
else appUsersManager.saveApiUser(peer);
}
}
if(contactsList && Array.isArray(contactsList) && contactsList.length) {
contactsList.forEach(userID => {
appUsersManager.pushContact(userID);
});
appUsersManager.contactsFillPromise = Promise.resolve(appUsersManager.contactsList);
}
if(messages) {
/* let tempID = this.tempID;
for(let message of messages) {
if(message.id < tempID) {
tempID = message.id;
}
}
if(tempID != this.tempID) {
this.log('Set tempID to:', tempID);
this.tempID = tempID;
} */
this.saveMessages(messages);
// FIX FILE_REFERENCE_EXPIRED KOSTIL'1999
for(let message of messages) {
if(message.media) {
this.wrapSingleMessage(message.mid, true);
}
}
}
if(allDialogsLoaded) {
this.dialogsStorage.allDialogsLoaded = allDialogsLoaded;
}
if(filters) {
this.filtersStorage.filters = filters;
}
if(dialogs) {
dialogs.forEachReverse(dialog => {
// @ts-ignore
//dialog.refetchTopMessage = true;
this.saveConversation(dialog);
});
}
apiUpdatesManager.attach(updates ?? null);
resolve();
}).catch(resolve).finally(() => {
setInterval(() => this.saveState(), 10000);
});
});
}
public saveState() {
const messages: any[] = [];
const dialogs: Dialog[] = [];
const peers: {[peerID: number]: any} = {};
for(const peerID in this.dialogsStorage.dialogs) {
let dialog = this.dialogsStorage.dialogs[peerID];
const historyStorage = this.historiesStorage[dialog.peerID];
const history = [].concat(historyStorage?.pending ?? [], historyStorage?.history ?? []);
dialog = copy(dialog);
let removeUnread = 0;
for(const mid of history) {
const message = this.getMessage(mid);
if(/* message._ != 'messageEmpty' && */message.id > 0) {
messages.push(message);
if(message.fromID != dialog.peerID) {
peers[message.fromID] = appPeersManager.getPeer(message.fromID);
}
dialog.top_message = message.mid;
break;
} else if(message.pFlags && message.pFlags.unread) {
++removeUnread;
}
}
if(removeUnread && dialog.unread_count) dialog.unread_count -= removeUnread;
dialogs.push(dialog);
peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID);
}
const us = apiUpdatesManager.updatesState;
const updates = {
seq: us.seq,
pts: us.pts,
date: us.date
};
const contactsList = [...appUsersManager.contactsList];
for(const userID of contactsList) {
if(!peers[userID]) {
peers[userID] = appUsersManager.getUser(userID);
}
}
const filters = this.filtersStorage.filters;
AppStorage.set({
state: {
dialogs,
messages,
allDialogsLoaded: this.dialogsStorage.allDialogsLoaded,
peers,
contactsList,
filters,
updates,
maxSeenMsgID: this.maxSeenID
}
});
}
public getInputEntities(entities: any) { public getInputEntities(entities: any) {
var sendEntites = copy(entities); var sendEntites = copy(entities);
@ -2232,13 +2086,27 @@ export class AppMessagesManager {
} }
public reloadConversation(peerID: number | number[]) { public reloadConversation(peerID: number | number[]) {
let peers = [].concat(peerID).map(peerID => appPeersManager.getInputPeerByID(peerID)); [].concat(peerID).forEach(peerID => {
if(!this.reloadConversationsPeers.includes(peerID)) {
this.reloadConversationsPeers.push(peerID);
this.log('will reloadConversation', peerID);
}
});
this.log('will reloadConversation', peerID); if(this.reloadConversationsPromise) return this.reloadConversationsPromise;
return this.reloadConversationsPromise = new Promise((resolve, reject) => {
setTimeout(() => {
let peers = this.reloadConversationsPeers.map(peerID => appPeersManager.getInputPeerByID(peerID));
this.reloadConversationsPeers.length = 0;
return apiManager.invokeApi('messages.getPeerDialogs', { apiManager.invokeApi('messages.getPeerDialogs', {peers}).then((result) => {
peers: peers this.applyConversations(result);
}).then(this.applyConversations.bind(this)); resolve();
}, reject).finally(() => {
this.reloadConversationsPromise = null;
});
}, 0);
});
} }
private doFlushHistory(inputPeer: any, justClear: boolean): Promise<true> { private doFlushHistory(inputPeer: any, justClear: boolean): Promise<true> {
@ -2819,7 +2687,6 @@ export class AppMessagesManager {
if(wasDialogBefore && wasDialogBefore.pFlags && wasDialogBefore.pFlags.pinned) { if(wasDialogBefore && wasDialogBefore.pFlags && wasDialogBefore.pFlags.pinned) {
if(!dialog.pFlags) dialog.pFlags = {}; if(!dialog.pFlags) dialog.pFlags = {};
dialog.pFlags.pinned = true; dialog.pFlags.pinned = true;
dialog.pinnedIndex = wasDialogBefore.pinnedIndex;
} }
this.saveConversation(dialog); this.saveConversation(dialog);
@ -3635,8 +3502,8 @@ export class AppMessagesManager {
this.newDialogsToHandle[peerID] = dialog; this.newDialogsToHandle[peerID] = dialog;
if(dialog.pFlags?.pinned) { if(dialog.pFlags?.pinned) {
delete dialog.pinnedIndex;
delete dialog.pFlags.pinned; delete dialog.pFlags.pinned;
this.dialogsStorage.pinnedOrders[folder_id].findAndSplice(p => p == dialog.peerID);
} }
dialog.folder_id = folder_id; dialog.folder_id = folder_id;
@ -3649,6 +3516,7 @@ export class AppMessagesManager {
} }
case 'updateDialogPinned': { case 'updateDialogPinned': {
const folderID = update.folder_id ?? 0;
this.log('updateDialogPinned', update); this.log('updateDialogPinned', update);
const peerID = appPeersManager.getPeerID(update.peer.peer); const peerID = appPeersManager.getPeerID(update.peer.peer);
const foundDialog = this.getDialogByPeerID(peerID); const foundDialog = this.getDialogByPeerID(peerID);
@ -3672,7 +3540,7 @@ export class AppMessagesManager {
if(!update.pFlags.pinned) { if(!update.pFlags.pinned) {
delete dialog.pFlags.pinned; delete dialog.pFlags.pinned;
delete dialog.pinnedIndex; this.dialogsStorage.pinnedOrders[folderID].findAndSplice(p => p == dialog.peerID);
} else { // means set } else { // means set
dialog.pFlags.pinned = true; dialog.pFlags.pinned = true;
} }
@ -3713,7 +3581,7 @@ export class AppMessagesManager {
//this.log('before order:', this.dialogsStorage[0].map(d => d.peerID)); //this.log('before order:', this.dialogsStorage[0].map(d => d.peerID));
this.dialogsStorage.pinnedIndexes[folderID] = 0; this.dialogsStorage.pinnedOrders[folderID].length = 0;
let willHandle = false; let willHandle = false;
update.order.reverse(); // index must be higher update.order.reverse(); // index must be higher
update.order.forEach((peer: any) => { update.order.forEach((peer: any) => {
@ -3728,7 +3596,6 @@ export class AppMessagesManager {
} }
const dialog = foundDialog[0]; const dialog = foundDialog[0];
delete dialog.pinnedIndex;
dialog.pFlags.pinned = true; dialog.pFlags.pinned = true;
this.dialogsStorage.generateIndexForDialog(dialog); this.dialogsStorage.generateIndexForDialog(dialog);

View File

@ -1,6 +1,6 @@
//import { logger } from "../polyfill"; //import { logger } from "../polyfill";
import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager"; import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager";
import { $rootScope } from "../utils"; import { $rootScope, findUpTag, findUpClassName } from "../utils";
import appImManager from "./appImManager"; import appImManager from "./appImManager";
import AppSearch, { SearchGroup } from "../../components/appSearch"; import AppSearch, { SearchGroup } from "../../components/appSearch";
import { parseMenuButtonsTo } from "../../components/misc"; import { parseMenuButtonsTo } from "../../components/misc";
@ -19,6 +19,8 @@ import AppEditFolderTab from "../../components/sidebarLeft/editFolder";
import AppIncludedChatsTab from "../../components/sidebarLeft/includedChats"; import AppIncludedChatsTab from "../../components/sidebarLeft/includedChats";
import SidebarSlider from "../../components/slider"; import SidebarSlider from "../../components/slider";
import SearchInput from "../../components/searchInput"; import SearchInput from "../../components/searchInput";
import appStateManager from "./appStateManager";
import appChatsManager from "./appChatsManager";
AvatarElement; AvatarElement;
@ -92,6 +94,11 @@ export class AppSidebarLeft extends SidebarSlider {
}; };
private globalSearch: AppSearch; private globalSearch: AppSearch;
// peerIDs
private recentSearch: number[] = [];
private recentSearchLoaded = false;
private recentSearchClearBtn: HTMLElement;
constructor() { constructor() {
super(document.getElementById('column-left') as HTMLDivElement, { super(document.getElementById('column-left') as HTMLDivElement, {
[AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab, [AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab,
@ -127,7 +134,41 @@ export class AppSidebarLeft extends SidebarSlider {
this.menuEl = this.toolsBtn.querySelector('.btn-menu'); this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu'); this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
this.globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups); this.globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups, (count) => {
if(!count && !this.searchInput.value.trim()) {
this.globalSearch.reset();
this.searchGroups.people.setActive();
this.renderRecentSearch();
}
});
this.searchContainer.addEventListener('click', (e) => {
const target = findUpTag(e.target, 'LI') as HTMLElement;
if(!target) {
return;
}
const searchGroup = findUpClassName(target, 'search-group');
if(!searchGroup || searchGroup.classList.contains('search-group-recent') || searchGroup.classList.contains('search-group-people')) {
return;
}
const peerID = +target.getAttribute('data-peerID');
if(this.recentSearch[0] != peerID) {
this.recentSearch.findAndSplice(p => p == peerID);
this.recentSearch.unshift(peerID);
if(this.recentSearch.length > 20) {
this.recentSearch.length = 20;
}
this.renderRecentSearch();
appStateManager.pushToState('recentSearch', this.recentSearch);
for(const peerID of this.recentSearch) {
appStateManager.pushPeer(peerID);
}
clearRecentSearchBtn.style.display = '';
}
}, {capture: true});
let peopleContainer = document.createElement('div'); let peopleContainer = document.createElement('div');
peopleContainer.classList.add('search-group-scrollable'); peopleContainer.classList.add('search-group-scrollable');
@ -160,6 +201,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.settings); this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.settings);
}); });
let firstTime = true;
this.searchInput.input.addEventListener('focus', (e) => { this.searchInput.input.addEventListener('focus', (e) => {
this.toolsBtn.classList.remove('active'); this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active'); this.backBtn.classList.add('active');
@ -167,6 +209,12 @@ export class AppSidebarLeft extends SidebarSlider {
void this.searchContainer.offsetWidth; // reflow void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active'); this.searchContainer.classList.add('active');
if(firstTime) {
this.searchGroups.people.setActive();
this.renderRecentSearch();
firstTime = false;
}
/* this.searchInput.addEventListener('blur', (e) => { /* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) { if(!this.searchInput.value) {
this.toolsBtn.classList.add('active'); this.toolsBtn.classList.add('active');
@ -181,13 +229,11 @@ export class AppSidebarLeft extends SidebarSlider {
this.toolsBtn.classList.add('active'); this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active'); this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active'); this.searchContainer.classList.remove('active');
firstTime = true;
setTimeout(() => { setTimeout(() => {
this.searchContainer.classList.add('hide'); this.searchContainer.classList.add('hide');
this.globalSearch.reset(); this.globalSearch.reset();
this.searchGroups.people.setActive();
//this.searchGroups.recent.setActive();
}, 150); }, 150);
}); });
@ -207,25 +253,45 @@ export class AppSidebarLeft extends SidebarSlider {
this.archivedCount.innerText = '' + e.detail.count; this.archivedCount.innerText = '' + e.detail.count;
}); });
appUsersManager.getTopPeers().then(categories => { appUsersManager.getTopPeers().then(peers => {
//console.log('got top categories:', categories); //console.log('got top categories:', categories);
peers.forEach((peerID) => {
let category = categories[0];
if(!category || !category.peers) {
return;
}
category.peers.forEach((topPeer: {
_: 'topPeer',
peer: any,
rating: number
}) => {
let peerID = appPeersManager.getPeerID(topPeer.peer);
let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true); let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true);
this.searchGroups.people.setActive(); this.searchGroups.people.setActive();
}); });
}); });
this.renderRecentSearch();
const clearRecentSearchBtn = this.recentSearchClearBtn = document.createElement('button');
clearRecentSearchBtn.classList.add('btn-icon', 'tgico-close');
this.searchGroups.recent.nameEl.append(clearRecentSearchBtn);
clearRecentSearchBtn.addEventListener('click', () => {
this.recentSearch = [];
appStateManager.pushToState('recentSearch', this.recentSearch);
this.renderRecentSearch();
clearRecentSearchBtn.style.display = 'none';
});
}
public renderRecentSearch() {
appStateManager.getState().then(state => {
if(state && !this.recentSearchLoaded && Array.isArray(state.recentSearch)) {
this.recentSearch = state.recentSearch;
this.recentSearchLoaded = true;
}
this.searchGroups.recent.list.innerHTML = '';
this.recentSearchClearBtn.style.display = this.recentSearch.length ? '' : 'none';
this.recentSearch.slice(0, 20).forEach(peerID => {
let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.recent.list, false, true, false, true);
dom.lastMessageSpan.innerText = peerID > 0 ? appUsersManager.getUserStatusString(peerID) : appChatsManager.getChatMembersString(peerID);
});
this.searchGroups.recent.setActive();
});
} }
} }

View File

@ -420,8 +420,8 @@ export class AppSidebarRight extends SidebarSlider {
private loadedAllMedia: {[type: string]: boolean} = {}; private loadedAllMedia: {[type: string]: boolean} = {};
public sharedMediaTypes = [ public sharedMediaTypes = [
'members', //'members',
//'inputMessagesFilterContacts', 'inputMessagesFilterContacts',
'inputMessagesFilterPhotoVideo', 'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument', 'inputMessagesFilterDocument',
'inputMessagesFilterUrl', 'inputMessagesFilterUrl',
@ -1145,9 +1145,9 @@ export class AppSidebarRight extends SidebarSlider {
}); });
} }
let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement; //let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement;
if(peerID > 0) { if(peerID > 0) {
membersLi.style.display = 'none'; //membersLi.style.display = 'none';
let user = appUsersManager.getUser(peerID); let user = appUsersManager.getUser(peerID);
if(user.phone && peerID != $rootScope.myID) { if(user.phone && peerID != $rootScope.myID) {
@ -1167,7 +1167,7 @@ export class AppSidebarRight extends SidebarSlider {
//this.log('userFull', userFull); //this.log('userFull', userFull);
}); });
} else { } else {
membersLi.style.display = appPeersManager.isBroadcast(peerID) ? 'none' : ''; //membersLi.style.display = appPeersManager.isBroadcast(peerID) ? 'none' : '';
let chat = appPeersManager.getPeer(peerID); let chat = appPeersManager.getPeer(peerID);
appProfileManager.getChatFull(chat.id).then((chatFull: any) => { appProfileManager.getChatFull(chat.id).then((chatFull: any) => {

View File

@ -0,0 +1,195 @@
import AppStorage from '../storage';
import appMessagesManager, { Dialog, DialogsStorage, FiltersStorage } from './appMessagesManager';
import appMessagesIDsManager from './appMessagesIDsManager';
import appPeersManager from './appPeersManager';
import appChatsManager from './appChatsManager';
import appUsersManager from './appUsersManager';
import apiUpdatesManager from './apiUpdatesManager';
import { copy } from '../utils';
import { logger } from '../polyfill';
export class AppStateManager {
public loaded: Promise<any>;
private log = logger('STATE'/* , LogLevels.error */);
private state: any = {};
private peers: {[peerID: number]: any} = {};
constructor() {
this.loadSavedState();
}
public loadSavedState() {
if(this.loaded) return this.loaded;
return this.loaded = new Promise((resolve, reject) => {
AppStorage.get<{
dialogs: Dialog[],
allDialogsLoaded: DialogsStorage['allDialogsLoaded'],
peers: any[],
messages: any[],
contactsList: number[],
updates: any,
filters: FiltersStorage['filters'],
maxSeenMsgID: number
}>('state').then((state) => {
const {dialogs, allDialogsLoaded, peers, messages, contactsList, maxSeenMsgID, updates, filters} = state;
this.state = state ?? {};
this.log('state res', dialogs, messages);
if(maxSeenMsgID && !appMessagesIDsManager.getMessageIDInfo(maxSeenMsgID)[1]) {
appMessagesManager.maxSeenID = maxSeenMsgID;
}
//return resolve();
if(peers) {
for(let peerID in peers) {
let peer = peers[peerID];
if(+peerID < 0) appChatsManager.saveApiChat(peer);
else appUsersManager.saveApiUser(peer);
}
}
if(contactsList && Array.isArray(contactsList) && contactsList.length) {
contactsList.forEach(userID => {
appUsersManager.pushContact(userID);
});
appUsersManager.contactsFillPromise = Promise.resolve(appUsersManager.contactsList);
}
if(messages) {
/* let tempID = this.tempID;
for(let message of messages) {
if(message.id < tempID) {
tempID = message.id;
}
}
if(tempID != this.tempID) {
this.log('Set tempID to:', tempID);
this.tempID = tempID;
} */
appMessagesManager.saveMessages(messages);
// FIX FILE_REFERENCE_EXPIRED KOSTIL'1999
for(let message of messages) {
if(message.media) {
appMessagesManager.wrapSingleMessage(message.mid, true);
}
}
}
if(allDialogsLoaded) {
appMessagesManager.dialogsStorage.allDialogsLoaded = allDialogsLoaded;
}
if(filters) {
for(const filterID in filters) {
appMessagesManager.filtersStorage.saveDialogFilter(filters[filterID], false);
}
}
if(dialogs) {
dialogs.forEachReverse(dialog => {
appMessagesManager.saveConversation(dialog);
});
}
apiUpdatesManager.attach(updates ?? null);
resolve(state);
}).catch(resolve).finally(() => {
setInterval(() => this.saveState(), 10000);
});
});
}
public getState() {
return this.loadSavedState();
}
public saveState() {
const messages: any[] = [];
const dialogs: Dialog[] = [];
const peers: {[peerID: number]: any} = this.peers;
for(const folderID in appMessagesManager.dialogsStorage.byFolders) {
const folder = appMessagesManager.dialogsStorage.getFolder(+folderID);
for(let dialog of folder) {
const historyStorage = appMessagesManager.historiesStorage[dialog.peerID];
const history = [].concat(historyStorage?.pending ?? [], historyStorage?.history ?? []);
dialog = copy(dialog);
let removeUnread = 0;
for(const mid of history) {
const message = appMessagesManager.getMessage(mid);
if(/* message._ != 'messageEmpty' && */message.id > 0) {
messages.push(message);
if(message.fromID != dialog.peerID) {
peers[message.fromID] = appPeersManager.getPeer(message.fromID);
}
dialog.top_message = message.mid;
break;
} else if(message.pFlags && message.pFlags.unread) {
++removeUnread;
}
}
if(removeUnread && dialog.unread_count) dialog.unread_count -= removeUnread;
dialogs.push(dialog);
peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID);
}
}
const us = apiUpdatesManager.updatesState;
const updates = {
seq: us.seq,
pts: us.pts,
date: us.date
};
const contactsList = [...appUsersManager.contactsList];
for(const userID of contactsList) {
if(!peers[userID]) {
peers[userID] = appUsersManager.getUser(userID);
}
}
const filters = appMessagesManager.filtersStorage.filters;
//const pinnedOrders = appMessagesManager.dialogsStorage.pinnedOrders;
AppStorage.set({
state: Object.assign({}, this.state, {
dialogs,
messages,
allDialogsLoaded: appMessagesManager.dialogsStorage.allDialogsLoaded,
peers,
contactsList,
filters,
//pinnedOrders,
updates,
maxSeenMsgID: appMessagesManager.maxSeenID
})
});
}
public pushToState(key: string, value: any) {
this.state[key] = value;
}
public pushPeer(peerID: number) {
this.peers[peerID] = appPeersManager.getPeer(peerID);
}
}
const appStateManager = new AppStateManager();
export default appStateManager;

View File

@ -6,6 +6,8 @@ import apiManager from '../mtproto/mtprotoworker';
import serverTimeManager from "../mtproto/serverTimeManager"; import serverTimeManager from "../mtproto/serverTimeManager";
import { formatPhoneNumber } from "../../components/misc"; import { formatPhoneNumber } from "../../components/misc";
import searchIndexManager from "../searchIndexManager"; import searchIndexManager from "../searchIndexManager";
import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager";
export type User = { export type User = {
_: 'user', _: 'user',
@ -45,6 +47,8 @@ export class AppUsersManager {
public contactsList: Set<number> = new Set(); public contactsList: Set<number> = new Set();
public myID: number; public myID: number;
public getPeersPromise: Promise<number[]>;
constructor() { constructor() {
apiManager.getUserID().then((id) => { apiManager.getUserID().then((id) => {
this.myID = id; this.myID = id;
@ -510,19 +514,39 @@ export class AppUsersManager {
}); });
} }
public getTopPeers() { public getTopPeers(): Promise<number[]> {
return apiManager.invokeApi('contacts.getTopPeers', { if(this.getPeersPromise) return this.getPeersPromise;
flags: 1,
correspondents: true,
offset: 0,
limit: 30,
hash: 0,
}).then((peers: any) => {
//console.log(peers);
this.saveApiUsers(peers.users);
appChatsManager.saveApiChats(peers.chats);
return peers.categories; return this.getPeersPromise = appStateManager.getState().then((state) => {
if(state?.topPeers?.length) {
return state.topPeers;
}
return apiManager.invokeApi('contacts.getTopPeers', {
flags: 1,
correspondents: true,
offset: 0,
limit: 30,
hash: 0,
}).then((result: any) => {
//console.log(result);
this.saveApiUsers(result.users);
appChatsManager.saveApiChats(result.chats);
const peerIDs = result.categories[0].peers.map((topPeer: {
_: 'topPeer',
peer: any,
rating: number
}) => {
const peerID = appPeersManager.getPeerID(topPeer.peer);
appStateManager.pushPeer(peerID);
return peerID;
});
appStateManager.pushToState('topPeers', peerIDs);
return peerIDs;
});
}); });
} }

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,8 @@ type RLottieOptions = {
loop?: boolean, loop?: boolean,
width?: number, width?: number,
height?: number, height?: number,
group?: string group?: string,
noCache?: true
}; };
export class RLottiePlayer { export class RLottiePlayer {
@ -96,13 +97,15 @@ export class RLottiePlayer {
} }
} }
// проверка на размер уже после скейлинга, сделано для попапа и сайдбарfа, где стикеры 80х80 и 68х68, туда нужно 75% if(!options.noCache) {
if(isApple && this.width > 100 && this.height > 100) { // проверка на размер уже после скейлинга, сделано для попапа и сайдбарfа, где стикеры 80х80 и 68х68, туда нужно 75%
this.cachingDelta = 2; //2 // 50% if(isApple && this.width > 100 && this.height > 100) {
} else if(this.width < 100 && this.height < 100) { this.cachingDelta = 2; //2 // 50%
this.cachingDelta = Infinity; // 100% } else if(this.width < 100 && this.height < 100) {
} else { this.cachingDelta = Infinity; // 100%
this.cachingDelta = 4; // 75% } else {
this.cachingDelta = 4; // 75%
}
} }
// if(isApple) { // if(isApple) {
@ -284,6 +287,10 @@ export class RLottiePlayer {
} else if(isSafari) { } else if(isSafari) {
this.sendQuery('renderFrame', frameNo); this.sendQuery('renderFrame', frameNo);
} else { } else {
if(!this.clamped.length) { // fix detached
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
}
this.sendQuery('renderFrame', frameNo, this.clamped); this.sendQuery('renderFrame', frameNo, this.clamped);
} }
} }
@ -471,6 +478,7 @@ class QueryableWorker {
} }
} }
//console.log('transfer', transfer);
this.worker.postMessage({ this.worker.postMessage({
'queryMethod': queryMethod, 'queryMethod': queryMethod,
'queryMethodArguments': args 'queryMethodArguments': args

View File

@ -321,6 +321,7 @@ class MTPNetworker {
}); });
} }
// тут можно сделать таймаут и выводить дисконнект
public pushMessage(message: { public pushMessage(message: {
msg_id: string, msg_id: string,
seq_no: number, seq_no: number,

View File

@ -55,7 +55,7 @@ export class Obfuscation {
this.encNew = new CTR(encKey, encIv); this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv); this.decNew = new CTR(decKey, decIv);
initPayload.set(intermediatePacketCodec.obfuscateTag, 56); initPayload.set(codec.obfuscateTag, 56);
const encrypted = this.encode(initPayload); const encrypted = this.encode(initPayload);
initPayload.set(encrypted.slice(56, 64), 56); initPayload.set(encrypted.slice(56, 64), 56);
@ -132,10 +132,8 @@ export default class Socket extends MTTransport {
constructor(dcID: number, url: string) { constructor(dcID: number, url: string) {
super(dcID, url); super(dcID, url);
this.log = logger(`WS-${dcID}`, LogLevels.log | LogLevels.error); this.log = logger(`WS-${dcID}`, LogLevels.log/* | LogLevels.error | LogLevels.debug */);
this.log('constructor'); this.log('constructor');
this.connect(); this.connect();
} }
@ -157,10 +155,14 @@ export default class Socket extends MTTransport {
handleOpen = () => { handleOpen = () => {
this.log('opened'); this.log('opened');
this.log.debug('sending init packet');
this.ws.send(this.obfuscation.init(this.codec)); this.ws.send(this.obfuscation.init(this.codec));
this.connected = true;
this.releasePending(); //setTimeout(() => {
this.connected = true;
this.releasePending();
//}, 3e3);
}; };
handleClose = (event: CloseEvent) => { handleClose = (event: CloseEvent) => {
@ -218,6 +220,8 @@ export default class Socket extends MTTransport {
send = (body: Uint8Array) => { send = (body: Uint8Array) => {
this.log.debug('-> body length to pending:', body.length); this.log.debug('-> body length to pending:', body.length);
//return;
if(this.networker) { if(this.networker) {
this.pending.push({body}); this.pending.push({body});
this.releasePending(); this.releasePending();

View File

@ -84,13 +84,13 @@ var markdownEntities = {
'__': 'messageEntityItalic' '__': 'messageEntityItalic'
} }
function getEmojiSpritesheetCoords(emojiCode) { function getEmojiSpritesheetCoords(emojiCode) {
let emojiInfo = emojiData[emojiCode.replace(/\ufe0f/g, '')]; let emojiInfo = emojiData[emojiCode/* .replace(/\ufe0f/g, '') */];
if(emojiInfo === undefined) { if(emojiInfo === undefined) {
//console.error('no emoji by code:', emojiCode, emojiCode && emojiCode.length, new TextEncoder().encode(emojiCode), emojiUnicode(emojiCode)); //console.error('no emoji by code:', emojiCode, emojiCode && emojiCode.length, new TextEncoder().encode(emojiCode), emojiUnicode(emojiCode));
return null; return null;
} }
return emojiUnicode(emojiCode); return emojiUnicode(emojiCode).replace(/(-fe0f|fe0f)/g, '');
} }
function parseEntities(text, options = {}) { function parseEntities(text, options = {}) {
var match; var match;

View File

@ -143,9 +143,9 @@ export function getRichElementValue (node, lines, line, selNode, selOffset) {
export const $rootScope = { export const $rootScope = {
$broadcast: (name/* : string */, detail/*? : any */) => { $broadcast: (name/* : string */, detail/*? : any */) => {
/* if(name != 'user_update') { if(name != 'user_update') {
console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail); console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail);
} */ }
let myCustomEvent = new CustomEvent(name, {detail}); let myCustomEvent = new CustomEvent(name, {detail});
document.dispatchEvent(myCustomEvent); document.dispatchEvent(myCustomEvent);
@ -463,7 +463,7 @@ export function calcImageInBox (imageW, imageH, boxW, boxH, noZooom) {
* @returns {String} The base 16 unicode code. * @returns {String} The base 16 unicode code.
*/ */
export function emojiUnicode(input) { export function emojiUnicode(input) {
let pairs = emojiUnicode.raw(input).split(' ').map(val => parseInt(val).toString(16)).filter(p => p != 'fe0f'); let pairs = emojiUnicode.raw(input).split(' ').map(val => parseInt(val).toString(16))/* .filter(p => p != 'fe0f') */;
if(pairs.length && pairs[0].length == 2) pairs[0] = '00' + pairs[0]; if(pairs.length && pairs[0].length == 2) pairs[0] = '00' + pairs[0];
return pairs.join('-'); return pairs.join('-');
} }

View File

@ -8,6 +8,7 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page'; import Page from './page';
import { App } from '../lib/mtproto/mtproto_config'; import { App } from '../lib/mtproto/mtproto_config';
import { mediaSizes } from '../lib/config';
let authCode: { let authCode: {
_: string, // 'auth.sentCode' _: string, // 'auth.sentCode'
@ -235,13 +236,14 @@ let onFirstMount = (): Promise<any> => {
}); });
let imageDiv = page.pageEl.querySelector('.auth-image') as HTMLDivElement; let imageDiv = page.pageEl.querySelector('.auth-image') as HTMLDivElement;
const size = mediaSizes.isMobile ? 100 : 166;
return Promise.all([ return Promise.all([
LottieLoader.loadAnimationFromURL({ LottieLoader.loadAnimationFromURL({
container: imageDiv, container: imageDiv,
loop: true, loop: true,
autoplay: true, autoplay: true,
width: 166, width: size,
height: 166 height: size
}, 'assets/img/TwoFactorSetupMonkeyIdle.tgs').then(animation => { }, 'assets/img/TwoFactorSetupMonkeyIdle.tgs').then(animation => {
idleAnimation = animation; idleAnimation = animation;
}), }),
@ -250,8 +252,8 @@ let onFirstMount = (): Promise<any> => {
container: imageDiv, container: imageDiv,
loop: false, loop: false,
autoplay: false, autoplay: false,
width: 166, width: size,
height: 166 height: size
}, 'assets/img/TwoFactorSetupMonkeyTracking.tgs').then(_animation => { }, 'assets/img/TwoFactorSetupMonkeyTracking.tgs').then(_animation => {
animation = _animation; animation = _animation;

View File

@ -7,6 +7,7 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
//import passwordManager from '../lib/mtproto/passwordManager'; //import passwordManager from '../lib/mtproto/passwordManager';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page'; import Page from './page';
import { mediaSizes } from '../lib/config';
let onFirstMount = (): Promise<any> => { let onFirstMount = (): Promise<any> => {
let needFrame = 0; let needFrame = 0;
@ -30,21 +31,23 @@ let onFirstMount = (): Promise<any> => {
}; };
toggleVisible.addEventListener('click', function(this, e) { toggleVisible.addEventListener('click', function(this, e) {
if(!passwordVisible) { passwordVisible = !passwordVisible;
if(passwordVisible) {
this.classList.add('tgico-eye2'); this.classList.add('tgico-eye2');
passwordInput.setAttribute('type', 'text'); passwordInput.setAttribute('type', 'text');
animation.setDirection(-1); animation.setDirection(1);
needFrame = 0; animation.curFrame = 0;
needFrame = 16;
animation.play(); animation.play();
} else { } else {
this.classList.remove('tgico-eye2'); this.classList.remove('tgico-eye2');
passwordInput.setAttribute('type', 'password'); passwordInput.setAttribute('type', 'password');
animation.setDirection(1); animation.setDirection(-1);
needFrame = 49; animation.curFrame = 16;
needFrame = 0;
animation.play(); animation.play();
} }
passwordVisible = !passwordVisible;
}); });
btnNext.addEventListener('click', function(this, e) { btnNext.addEventListener('click', function(this, e) {
@ -90,14 +93,18 @@ let onFirstMount = (): Promise<any> => {
/* passwordInput.addEventListener('input', function(this, e) { /* passwordInput.addEventListener('input', function(this, e) {
}); */ }); */
const size = mediaSizes.isMobile ? 100 : 166;
return Promise.all([ return Promise.all([
LottieLoader.loadAnimationFromURL({ LottieLoader.loadAnimationFromURL({
container: page.pageEl.querySelector('.auth-image'), container: page.pageEl.querySelector('.auth-image'),
loop: false, loop: false,
autoplay: false, autoplay: false,
width: 166, width: size,
height: 166 height: size,
}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => { noCache: true
//}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
}, 'assets/img/TwoFactorSetupMonkeyPeek.tgs').then(_animation => {
//return;
animation = _animation; animation = _animation;
animation.addListener('enterFrame', currentFrame => { animation.addListener('enterFrame', currentFrame => {
//console.log('enterFrame', e, needFrame); //console.log('enterFrame', e, needFrame);
@ -110,7 +117,7 @@ let onFirstMount = (): Promise<any> => {
}); });
needFrame = 49; needFrame = 49;
animation.play(); //animation.play();
}) })
]); ]);
}; };

View File

@ -272,7 +272,7 @@
color: #fff; color: #fff;
border-radius: 12px; border-radius: 12px;
margin-top: 4px; margin-top: 4px;
margin-right: -2px; margin-right: -3px;
flex: 0 0 auto; flex: 0 0 auto;
} }

View File

@ -2,28 +2,53 @@
//display: flex; //display: flex;
flex-direction: column; flex-direction: column;
.folders-tabs-scrollable {
position: sticky;
top: -1px;
z-index: 1;
background-color: #fff;
.scrollable {
position: relative;
border-bottom: 1px solid #dadce0;
}
.menu-horizontal {
border-bottom: none;
ul {
justify-content: space-between
}
li {
padding: 9px 16px 7px 16px;
display: flex;
justify-content: center;
}
}
span.unread-count {
margin-left: 5px;
background: #50a2e9;
height: 20px;
border-radius: 12px;
font-weight: 500;
color: white;
line-height: 22px;
margin-top: 3px;
min-width: 20px;
padding: 0 6px;
&:empty {
display: none;
}
}
}
#chats-container { #chats-container {
max-height: 100%; max-height: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
.folders-tabs-scrollable {
position: sticky;
top: 0;
z-index: 1;
.scrollable {
position: relative;
}
}
.menu-horizontal {
background: #fff;
ul {
justify-content: space-between
}
}
} }
.sidebar-slider { .sidebar-slider {
@ -216,6 +241,11 @@
width: 380px; width: 380px;
margin: 0 auto; margin: 0 auto;
flex: 0 0 auto; flex: 0 0 auto;
@include respond-to(handhelds) {
width: 100%;
padding: 0 16px;
}
} }
.chats-container { .chats-container {
@ -227,6 +257,15 @@
margin-top: 14px; margin-top: 14px;
margin-left: 23px; margin-left: 23px;
color: #707579; color: #707579;
padding-right: 24px;
}
}
.edit-folder-container {
.input-wrapper {
width: 380px;
margin: 0 auto;
flex: 0 0 auto;
} }
} }
@ -290,6 +329,10 @@
margin-left: 1.438rem; margin-left: 1.438rem;
line-height: 1.2; line-height: 1.2;
padding-bottom: 1.438rem; padding-bottom: 1.438rem;
@include respond-to(handhelds) {
padding-right: 24px;
}
} }
.sidebar-left-h2 { .sidebar-left-h2 {
@ -314,7 +357,7 @@
.sticker-container { .sticker-container {
width: 86px; width: 86px;
height: 86px; height: 86px;
margin: 1px auto 32px; margin: 1px auto 29px;
flex: 0 0 auto; flex: 0 0 auto;
} }
@ -328,8 +371,9 @@
.sidebar-left-h2 { .sidebar-left-h2 {
color: #707579; color: #707579;
font-size: 15px; font-size: 15px;
padding-top: 7px; // padding-top: 7px;
padding-bottom: 15px; // padding-bottom: 15px;
padding: 7px 16px 15px 16px;
font-weight: 500; font-weight: 500;
} }
} }
@ -351,22 +395,27 @@
margin-right: 6px; margin-right: 6px;
} }
.folders-container { // .folders-container {
padding: 0 16px; // padding: 0 16px;
} // }
.category { .category {
padding: 7px 0 11px 0; padding: 7px 16px 11px 16px;
display: flex; display: flex;
padding-bottom: 11px; //padding-bottom: 11px;
justify-content: space-between; justify-content: space-between;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
margin-bottom: 10px;
p {
height: unset;
}
p:last-child { p:last-child {
color: #707579; color: #707579;
font-size: 14px; font-size: 14px;
line-height: 1; line-height: 20px;
} }
.btn-primary { .btn-primary {
@ -386,7 +435,8 @@
@include respond-to(handhelds) { @include respond-to(handhelds) {
.input-wrapper { .input-wrapper {
width: 328px; width: 100%;
padding: 0 16px;
} }
.input-field input { .input-field input {
@ -410,6 +460,10 @@
.rp { .rp {
padding: 8px 3px !important; padding: 8px 3px !important;
height: 48px !important; height: 48px !important;
@include respond-to(handhelds) {
padding: 8px 12px !important;
}
} }
} }
@ -429,6 +483,21 @@
.folder-categories { .folder-categories {
width: 100%; width: 100%;
.checkbox {
margin-top: -9px !important;;
right: 0;
position: absolute;
[type="checkbox"]+span {
padding-left: 38px;
}
[type="checkbox"]:checked+span:before {
top: 5px;
left: 0px;
}
}
} }
.folder-category-button { .folder-category-button {
@ -440,6 +509,7 @@
user-select: none; user-select: none;
margin-left: 32px; margin-left: 32px;
font-size: 16px; font-size: 16px;
flex: 1 1 auto;
} }
&.blue, &.blue:before { &.blue, &.blue:before {
@ -466,31 +536,64 @@
font-weight: 500; font-weight: 500;
padding: 6px 0 8px 16px; padding: 6px 0 8px 16px;
} }
.selector {
ul {
li > .rp {
margin: 0 !important;
padding: 7px 12px !important;
height: 62px;
}
.dialog-avatar {
width: 46px;
height: 46px;
}
span.user-title {
font-weight: 500;
}
.user-caption {
padding: 0px 0px 0 14px;
margin-top: -2px;
}
span.user-last-message {
font-size: 15px;
margin-top: 2px;
}
ul { .checkbox {
li > .rp { margin-top: 10px;
margin: 0 !important; }
padding: 7px 12px !important;
height: 62px;
}
.dialog-avatar { [type="checkbox"]+span {
width: 46px; padding-left: 26px;
height: 46px; }
}
span.user-title {
font-weight: 500;
}
.user-caption {
padding: 0px 0px 0 14px;
margin-top: -2px;
}
span.user-last-message {
font-size: 15px;
margin-top: -1px;
} }
} }
.checkbox [type="checkbox"]+span:after {
border-radius: 50%;
height: 20px;
width: 20px;
border-color: #dadbdc;
}
.checkbox [type="checkbox"]:checked+span:after {
background-color: #4EA4F6;
border: none;
}
.folder-category-button {
cursor: pointer;
}
}
.search-group-recent {
.search-group__name {
display: flex;
justify-content: space-between;
align-items: center;
}
} }

View File

@ -294,7 +294,7 @@
opacity: 1; opacity: 1;
transition: opacity .2s ease; transition: opacity .2s ease;
&.thumbnail { html:not(.is-mac) &.thumbnail {
filter: blur(7px); filter: blur(7px);
} }
} }

View File

@ -24,6 +24,10 @@
position: relative; position: relative;
max-height: inherit; // fix safari max-height: inherit; // fix safari
} }
avatar-element:before {
font-size: 18px;
}
} }
&-search { &-search {

View File

@ -2,12 +2,27 @@
max-width: 720px; // 360 + 360 / 2 max-width: 720px; // 360 + 360 / 2
overflow: hidden; overflow: hidden;
.btn-primary {
@include respond-to(handhelds) {
height: 50px;
}
}
.subtitle { .subtitle {
margin: 0; margin: 0;
@include respond-to(handhelds) {
font-size: 14px;
}
} }
.input-wrapper { .input-wrapper {
margin-top: 49px; margin-top: 49px;
@include respond-to(handhelds) {
margin-top: 41px;
width: 100%;
padding: 0 16px;
}
} }
.tabs-container { .tabs-container {
@ -50,6 +65,18 @@
height: auto; height: auto;
} */ } */
} }
.page-password {
.input-wrapper {
@include respond-to(handhelds) {
margin-top: 31px;
}
.btn-primary {
margin-top: 1rem;
}
}
}
} }
.page-sign, .page-signUp { .page-sign, .page-signUp {
@ -85,6 +112,15 @@
} }
} }
.page-signQR {
.auth-image {
@include respond-to(handhelds) {
width: 166px;
height: 166px;
}
}
}
/* .page-signQR { /* .page-signQR {
.auth-image { .auth-image {
position: relative; position: relative;
@ -106,5 +142,16 @@
.auth-image { .auth-image {
margin-top: 10px; margin-top: 10px;
margin-bottom: 14px; margin-bottom: 14px;
@include respond-to(handhelds) {
margin-bottom: 24px;
}
}
#signUp {
@include respond-to(handhelds) {
margin-top: 100px;
}
} }
} }

View File

@ -152,4 +152,10 @@
.popup-create-poll.popup-new-media .btn-primary { .popup-create-poll.popup-new-media .btn-primary {
width: 94px; width: 94px;
}
.popup-new-media.popup-send-photo {
.popup-header {
padding: 0;
}
} }

View File

@ -202,6 +202,11 @@ h4 {
//margin: 1.5rem 0 1rem 0; //margin: 1.5rem 0 1rem 0;
margin: 22px 0 14px; margin: 22px 0 14px;
line-height: 110%; line-height: 110%;
@include respond-to(handhelds) {
font-size: 20px;
margin: 2px 0 8px;
}
} }
input { input {
@ -766,7 +771,7 @@ avatar-element {
height: 0; height: 0;
width: 0; width: 0;
border: solid #bdbdbd; border: solid #707579;
border-radius: 1px; border-radius: 1px;
border-width: 0 2px 2px 0; border-width: 0 2px 2px 0;
display: inline-block; display: inline-block;
@ -805,6 +810,10 @@ avatar-element {
transition: .2s border-color; transition: .2s border-color;
position: relative; position: relative;
z-index: 1; z-index: 1;
@include respond-to(handhelds) {
height: 50px;
}
/* font-weight: 500; */ /* font-weight: 500; */
/* &:hover { /* &:hover {
@ -869,9 +878,13 @@ avatar-element {
margin: 1.25rem 0; margin: 1.25rem 0;
display: block; display: block;
text-align: left; text-align: left;
padding: 0 19px; padding: 0 18px;
/* font-weight: 500; */ /* font-weight: 500; */
position: relative; position: relative;
@include respond-to(handhelds) {
margin-bottom: 27px;
}
} }
[type="checkbox"] { [type="checkbox"] {
@ -1036,6 +1049,11 @@ input:focus, button:focus {
width: 166px; width: 166px;
height: 166px; height: 166px;
margin: 0 auto 18px; margin: 0 auto 18px;
@include respond-to(handhelds) {
width: 120px;
height: 120px;
}
} }
/* .phone-wrapper { /* .phone-wrapper {
@ -1053,6 +1071,10 @@ input:focus, button:focus {
cursor: pointer; cursor: pointer;
font-size: 1.5rem; font-size: 1.5rem;
@include respond-to(handhelds) {
margin-top: -14px;
}
html.no-touch &:hover { html.no-touch &:hover {
opacity: 1; opacity: 1;
} }