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

View File

@ -208,7 +208,7 @@ export class AppSelectPeers {
}
public add(peerID: any, title?: string) {
console.trace('add');
//console.trace('add');
const div = document.createElement('div');
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 {
public audio: HTMLAudioElement;
public preloader: ProgressivePreloader;
private attachedHandlers: {[name: string]: any[]} = {};
private onTypeDisconnect: () => void;
@ -303,7 +304,7 @@ export default class AudioElement extends HTMLElement {
const audioTimeDiv = this.querySelector('.audio-time') as HTMLDivElement;
audioTimeDiv.innerHTML = durationStr;
let preloader: ProgressivePreloader;
let preloader: ProgressivePreloader = this.preloader;
let promise: CancellablePromise<Blob>;
const onLoad = () => {
@ -377,7 +378,8 @@ export default class AudioElement extends HTMLElement {
this.addEventListener('click', onClick);
this.click();
} 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];
}
this.preloader = null;
}
static get observedAttributes(): string[] {

View File

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

View File

@ -3,7 +3,7 @@ import { horizontalMenu, renderImageFromUrl, putPreloader } from "./misc";
import lottieLoader from "../lib/lottieLoader";
//import Scrollable from "./scrollable";
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 appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
//import apiManager from '../lib/mtproto/apiManager';
@ -16,6 +16,7 @@ import Config, { touchSupport } from "../lib/config";
import { MTDocument } from "../types";
import animationIntersector from "./animationIntersector";
import appSidebarRight from "../lib/appManagers/appSidebarRight";
import appStateManager from "../lib/appManagers/appStateManager";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -27,6 +28,12 @@ interface EmoticonsTab {
class EmojiTab implements EmoticonsTab {
public content: HTMLElement;
private recent: string[] = [];
private recentItemsDiv: HTMLElement;
private heights: number[] = [];
private scroll: Scrollable;
init() {
this.content = document.getElementById('content-emoji') as HTMLDivElement;
@ -37,7 +44,9 @@ class EmojiTab implements EmoticonsTab {
const sorted: {
[category: string]: string[]
} = {};
} = {
'Recent': []
};
for(const emoji in Config.Emoji) {
const details = Config.Emoji[emoji];
@ -72,46 +81,48 @@ class EmojiTab implements EmoticonsTab {
const emojis = sorted[category];
emojis.forEach(emoji => {
//const emoji = details.unified;
//const emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
/* if(emojiUnicode(emoji) == '1f481-200d-2642') {
console.log('append emoji', emoji, emojiUnicode(emoji));
} */
const spanEmoji = document.createElement('span');
const kek = RichTextProcessor.wrapRichText(emoji);
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv);
if(!kek.includes('emoji')) {
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji));
return;
}
//console.log(kek);
spanEmoji.innerHTML = kek;
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
itemsDiv.appendChild(spanEmoji);
/* if(category == 'Smileys & Emotion') {
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
} */
});
divs[category] = div;
}
//console.timeEnd('emojiParse');
const heights: number[] = [0];
let prevCategoryIndex = 1;
let prevCategoryIndex = 0;
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) => {
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
prevCategoryIndex = EmoticonsDropdown.contentOnScroll(menu, this.heights, prevCategoryIndex, emojiScroll.container);
});
//emojiScroll.setVirtualContainer(emojiScroll.container);
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();
this.recentItemsDiv = divs['Recent'].querySelector('.category-items');
for(const emoji of this.recent) {
this.appendEmoji(emoji, this.recentItemsDiv);
}
categories.unshift('Recent');
categories.map(category => {
const div = divs[category];
@ -123,27 +134,86 @@ class EmojiTab implements EmoticonsTab {
return div;
}).forEach(div => {
//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);
EmoticonsDropdown.menuOnClick(menu, heights, emojiScroll);
EmoticonsDropdown.menuOnClick(menu, this.heights, emojiScroll);
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) => {
let target = e.target as any;
let target = e.target as HTMLElement;
//if(target.tagName != 'SPAN') return;
if(target.tagName == 'SPAN' && !target.classList.contains('emoji')) {
target = target.firstElementChild;
target = target.firstElementChild as HTMLElement;
} else if(target.tagName == 'DIV') return;
//console.log('contentEmoji div', target);
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});
appImManager.chatInputC.messageInput.dispatchEvent(event);
};
@ -685,6 +755,7 @@ class EmoticonsDropdown {
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
/* dropdown.classList.remove('active');
toggleEl.classList.remove('active'); */
emoticonsDropdown.toggle(false);
} else {
console.warn('got no doc by id:', fileID);
}

View File

@ -111,8 +111,10 @@ export default class ProgressivePreloader {
return;
}
let totalLength = this.circle.getTotalLength();
//console.log('setProgress', (percents / 100 * totalLength));
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200';
try {
let totalLength = this.circle.getTotalLength();
//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]
}
});
this.getSuggestedFilters();
}
private getSuggestedFilters() {

View File

@ -31,7 +31,10 @@ export default class AppSettingsTab implements SliderTab {
});
this.logOutBtn.addEventListener('click', (e) => {
apiManager.logOut();
apiManager.logOut().finally(() => {
localStorage.clear();
location.reload();
});
});
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 => {
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)), '');
let c = categories[category] === undefined ? 9 : categories[category];

View File

@ -77,7 +77,7 @@
<p class="subtitle sent-type"></p>
<div class="input-wrapper">
<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>
</div>
</div>
@ -214,7 +214,7 @@
<div class="folders-tabs-scrollable">
<nav class="menu-horizontal" style="display: none;" id="folders-tabs">
<ul>
<li class="rp"><span>All</span></li>
<li class="rp"><span>All</span><span class="unread-count"></span></li>
</ul>
</nav>
</div>
@ -268,12 +268,12 @@
</div>
<div class="input-wrapper">
<div class="input-field">
<input type="text" name="name" class="new-channel-name" autocomplete="xxDDqqOX" required="">
<label for="name">Channel Name</label>
<input type="text" name="name" class="new-channel-name" id="new-channel-name" autocomplete="xxDDqqOX" required="">
<label for="new-channel-name">Channel Name</label>
</div>
<div class="input-field">
<input type="text" name="description" class="new-channel-description" autocomplete="aintsofunnow" required="">
<label for="lastName">Description (optional)</label>
<input type="text" name="description" class="new-channel-description" id="new-channel-description" autocomplete="aintsofunnow" required="">
<label for="new-channel-description">Description (optional)</label>
</div>
</div>
<div class="caption">You can provide an optional description for your channel.</div>
@ -301,8 +301,8 @@
</div>
<div class="input-wrapper">
<div class="input-field">
<input type="text" name="name" class="new-group-name" autocomplete="feellikeamonster2112" required="">
<label for="name">Group Name</label>
<input type="text" name="name" class="new-group-name" id="new-group-name" autocomplete="feellikeamonster2112" required="">
<label for="new-group-name">Group Name</label>
</div>
</div>
<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 url: string;
private log = logger('MP4', LogLevels.error);
private log = logger('MP4'/* , LogLevels.error */);
//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 { PopupButton, PopupPeer } from "../../components/popup";
import { SliderTab } from "../../components/slider";
import appStateManager from "./appStateManager";
type DialogDom = {
avatarEl: AvatarElement,
@ -330,7 +331,7 @@ export class AppDialogsManager {
public chatsContainer = document.getElementById('chats-container') as HTMLDivElement;
private chatsPreloader: HTMLDivElement;
public loadDialogsPromise: ReturnType<AppMessagesManager["getConversations"]>;
public loadDialogsPromise: Promise<any>;
public loadedAll = false;
public scroll: Scrollable = null;
@ -351,15 +352,21 @@ export class AppDialogsManager {
};
private filtersRendered: {
[filterID: string]: {
menu: HTMLElement, container: HTMLElement
menu: HTMLElement,
container: HTMLElement,
unread: HTMLElement
}
} = {};
private showFiltersTimeout: number;
private allUnreadCount: HTMLElement;
private accumulateArchivedTimeout: number;
constructor() {
this.chatList.addEventListener('contextmenu', this.contextMenu.onContextMenu);
this.chatsPreloader = putPreloader(null, true);
this.allUnreadCount = this.folders.menu.querySelector('.unread-count');
if(USEPINNEDDELIMITER) {
this.pinnedDelimiter = document.createElement('div');
@ -423,6 +430,7 @@ export class AppDialogsManager {
this.setDialogPosition(dialog);
this.setPinnedDelimiter();
this.setFiltersUnreadCount();
});
$rootScope.$on('dialog_flush', (e: CustomEvent) => {
@ -431,6 +439,7 @@ export class AppDialogsManager {
if(dialog) {
this.setLastMessage(dialog);
this.validateForFilter();
this.setFiltersUnreadCount();
}
});
@ -444,6 +453,7 @@ export class AppDialogsManager {
this.setPinnedDelimiter();
this.validateForFilter();
this.setFiltersUnreadCount();
});
$rootScope.$on('dialog_drop', (e: CustomEvent) => {
@ -455,6 +465,8 @@ export class AppDialogsManager {
delete this.doms[peerID];
this.scroll.reorder();
}
this.setFiltersUnreadCount();
});
$rootScope.$on('dialog_unread', (e: CustomEvent) => {
@ -472,6 +484,7 @@ export class AppDialogsManager {
}
this.validateForFilter();
this.setFiltersUnreadCount();
}
});
@ -505,6 +518,7 @@ export class AppDialogsManager {
const dialog = folder[i];
this.updateDialog(dialog);
}
this.setFiltersUnreadCount();
}
});
@ -549,6 +563,7 @@ export class AppDialogsManager {
if(this.filterID == id) return;
this.chatLists[id].innerHTML = '';
this.scroll.setVirtualContainer(this.chatLists[id]);
this.filterID = id;
this.onTabChange();
@ -563,7 +578,7 @@ export class AppDialogsManager {
//selectTab(0);
(this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click();
/* false && */appMessagesManager.loadSavedState().then(() => {
/* false && */appStateManager.loadSavedState().then(() => {
return appMessagesManager.filtersStorage.getDialogFilters();
}).then(filters => {
for(const filterID in filters) {
@ -601,6 +616,26 @@ export class AppDialogsManager {
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 span = document.createElement('span');
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);
this.folders.menu.firstElementChild.append(li);
@ -650,13 +687,18 @@ export class AppDialogsManager {
this.setListClickListener(ul);
ul.addEventListener('contextmenu', this.contextMenu.onContextMenu);
setTimeout(() => {
this.folders.menu.style.display = '';
}, 0);
if(!this.showFiltersTimeout) {
this.showFiltersTimeout = setTimeout(() => {
this.showFiltersTimeout = 0;
this.folders.menu.style.display = '';
this.setFiltersUnreadCount();
}, 0);
}
this.filtersRendered[filter.id] = {
menu: li,
container: div
container: div,
unread: unreadSpan
};
}
@ -688,9 +730,14 @@ export class AppDialogsManager {
//console.time('getDialogs time');
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');
@ -1170,6 +1217,11 @@ export class AppDialogsManager {
this.doms[dialog.peerID] = dom;
if($rootScope.selectedPeerID == peerID) {
li.classList.add('active');
this.lastActiveListElement = li;
}
/* if(container) {
container.append(li);
} */

View File

@ -294,7 +294,11 @@ class AppDocsManager {
};
promise.then(() => {
deferred.resolve(this.createMP4Stream(doc));
if(doc.url) { // может быть уже загружен из кэша
deferred.resolve();
} else {
deferred.resolve(this.createMP4Stream(doc));
}
});
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.add('is-sent');
bubble.dataset.mid = mid;
@ -1404,12 +1410,12 @@ export class AppImManager {
if(samePeer) {
if(this.bubbles[lastMsgID]) {
if(dialog && lastMsgID == topMessage) {
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight;
} else if(isTarget) {
if(isTarget) {
this.scrollable.scrollIntoView(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;
@ -1503,14 +1509,15 @@ export class AppImManager {
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) {
clearTimeout(this.scrollable.scrollLocked);
this.scrollable.scrollLocked = 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)) {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
}
@ -2209,9 +2216,14 @@ export class AppImManager {
let doc = appDocsManager.getDoc(message.id);
this.log('will wrap pending doc:', doc);
let docDiv = wrapDocument(doc, false, true, message.id);
let icoDiv = docDiv.querySelector('.audio-download, .document-ico');
preloader.attach(icoDiv, false);
if(doc.type == 'audio' || doc.type == 'voice') {
// @ts-ignore
docDiv.preloader = preloader;
} else {
let icoDiv = docDiv.querySelector('.audio-download, .document-ico');
preloader.attach(icoDiv, false);
}
if(pending.type == 'voice') {
bubble.classList.add('bubble-audio');
@ -2735,7 +2747,10 @@ export class AppImManager {
promise = result.then((result) => {
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');
if(this.peerID != peerID) {

View File

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

View File

@ -59,7 +59,6 @@ export type Dialog = {
index: number,
peerID: number,
pinnedIndex: number,
pFlags: Partial<{
pinned: true,
unread_mark: true
@ -67,15 +66,15 @@ export type Dialog = {
pts: number
}
class DialogsStorage {
export class DialogsStorage {
public dialogs: {[peerID: string]: Dialog} = {};
public byFolders: {[folderID: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {};
public dialogsOffsetDate: {[folder_id: number]: number} = {};
public pinnedIndexes: {[folder_id: number]: number} = {
0: 0,
1: 0
public pinnedOrders: {[folder_id: number]: number[]} = {
0: [],
1: []
};
public dialogsNum = 0;
@ -176,26 +175,14 @@ class DialogsStorage {
}
public generateDialogPinnedDateByIndex(pinnedIndex: number) {
return 0x7fffff00 + (pinnedIndex & 0xff);
return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов
}
public generateDialogPinnedDate(dialog?: Dialog) {
const folderID = 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]++;
}
public generateDialogPinnedDate(dialog: Dialog) {
const order = this.pinnedOrders[dialog.folder_id];
if(pinnedIndex > this.pinnedIndexes[folderID]) {
this.pinnedIndexes[folderID] = pinnedIndex;
}
const foundIndex = order.indexOf(dialog.peerID);
const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex;
return this.generateDialogPinnedDateByIndex(pinnedIndex);
}
@ -268,7 +255,7 @@ export type DialogFilter = {
include_peers: number[],
exclude_peers: number[]
};
class FiltersStorage {
export class FiltersStorage {
public filters: {[filterID: string]: DialogFilter} = {};
constructor() {
@ -465,11 +452,18 @@ class FiltersStorage {
}
public getOutputDialogFilter(filter: DialogFilter) {
const c = copy(filter);
const c: DialogFilter = copy(filter);
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
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;
}
@ -500,6 +494,14 @@ class FiltersStorage {
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]) {
// ну давай же найдём различия теперь, раз они сами не хотят приходить
const oldFilter = this.filters[filter.id];
@ -578,7 +580,8 @@ export class AppMessagesManager {
public newDialogsToHandle: {[peerID: string]: {reload: true} | Dialog} = {};
public newUpdatesAfterReloadToHandle: any = {};
public loaded: Promise<any> = null;
private reloadConversationsPromise: Promise<void>;
private reloadConversationsPeers: number[] = [];
private dialogsIndex = searchIndexManager.createIndex();
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) {
var sendEntites = copy(entities);
@ -2232,13 +2086,27 @@ export class AppMessagesManager {
}
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', {
peers: peers
}).then(this.applyConversations.bind(this));
apiManager.invokeApi('messages.getPeerDialogs', {peers}).then((result) => {
this.applyConversations(result);
resolve();
}, reject).finally(() => {
this.reloadConversationsPromise = null;
});
}, 0);
});
}
private doFlushHistory(inputPeer: any, justClear: boolean): Promise<true> {
@ -2819,7 +2687,6 @@ export class AppMessagesManager {
if(wasDialogBefore && wasDialogBefore.pFlags && wasDialogBefore.pFlags.pinned) {
if(!dialog.pFlags) dialog.pFlags = {};
dialog.pFlags.pinned = true;
dialog.pinnedIndex = wasDialogBefore.pinnedIndex;
}
this.saveConversation(dialog);
@ -3635,8 +3502,8 @@ export class AppMessagesManager {
this.newDialogsToHandle[peerID] = dialog;
if(dialog.pFlags?.pinned) {
delete dialog.pinnedIndex;
delete dialog.pFlags.pinned;
this.dialogsStorage.pinnedOrders[folder_id].findAndSplice(p => p == dialog.peerID);
}
dialog.folder_id = folder_id;
@ -3649,6 +3516,7 @@ export class AppMessagesManager {
}
case 'updateDialogPinned': {
const folderID = update.folder_id ?? 0;
this.log('updateDialogPinned', update);
const peerID = appPeersManager.getPeerID(update.peer.peer);
const foundDialog = this.getDialogByPeerID(peerID);
@ -3672,7 +3540,7 @@ export class AppMessagesManager {
if(!update.pFlags.pinned) {
delete dialog.pFlags.pinned;
delete dialog.pinnedIndex;
this.dialogsStorage.pinnedOrders[folderID].findAndSplice(p => p == dialog.peerID);
} else { // means set
dialog.pFlags.pinned = true;
}
@ -3713,7 +3581,7 @@ export class AppMessagesManager {
//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;
update.order.reverse(); // index must be higher
update.order.forEach((peer: any) => {
@ -3728,7 +3596,6 @@ export class AppMessagesManager {
}
const dialog = foundDialog[0];
delete dialog.pinnedIndex;
dialog.pFlags.pinned = true;
this.dialogsStorage.generateIndexForDialog(dialog);

View File

@ -1,6 +1,6 @@
//import { logger } from "../polyfill";
import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager";
import { $rootScope } from "../utils";
import { $rootScope, findUpTag, findUpClassName } from "../utils";
import appImManager from "./appImManager";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import { parseMenuButtonsTo } from "../../components/misc";
@ -19,6 +19,8 @@ import AppEditFolderTab from "../../components/sidebarLeft/editFolder";
import AppIncludedChatsTab from "../../components/sidebarLeft/includedChats";
import SidebarSlider from "../../components/slider";
import SearchInput from "../../components/searchInput";
import appStateManager from "./appStateManager";
import appChatsManager from "./appChatsManager";
AvatarElement;
@ -92,6 +94,11 @@ export class AppSidebarLeft extends SidebarSlider {
};
private globalSearch: AppSearch;
// peerIDs
private recentSearch: number[] = [];
private recentSearchLoaded = false;
private recentSearchClearBtn: HTMLElement;
constructor() {
super(document.getElementById('column-left') as HTMLDivElement, {
[AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab,
@ -127,7 +134,41 @@ export class AppSidebarLeft extends SidebarSlider {
this.menuEl = this.toolsBtn.querySelector('.btn-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');
peopleContainer.classList.add('search-group-scrollable');
@ -160,6 +201,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.settings);
});
let firstTime = true;
this.searchInput.input.addEventListener('focus', (e) => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
@ -167,6 +209,12 @@ export class AppSidebarLeft extends SidebarSlider {
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
if(firstTime) {
this.searchGroups.people.setActive();
this.renderRecentSearch();
firstTime = false;
}
/* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
@ -181,13 +229,11 @@ export class AppSidebarLeft extends SidebarSlider {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
firstTime = true;
setTimeout(() => {
this.searchContainer.classList.add('hide');
this.globalSearch.reset();
this.searchGroups.people.setActive();
//this.searchGroups.recent.setActive();
}, 150);
});
@ -207,25 +253,45 @@ export class AppSidebarLeft extends SidebarSlider {
this.archivedCount.innerText = '' + e.detail.count;
});
appUsersManager.getTopPeers().then(categories => {
appUsersManager.getTopPeers().then(peers => {
//console.log('got top categories:', categories);
let category = categories[0];
if(!category || !category.peers) {
return;
}
category.peers.forEach((topPeer: {
_: 'topPeer',
peer: any,
rating: number
}) => {
let peerID = appPeersManager.getPeerID(topPeer.peer);
peers.forEach((peerID) => {
let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true);
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} = {};
public sharedMediaTypes = [
'members',
//'inputMessagesFilterContacts',
//'members',
'inputMessagesFilterContacts',
'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument',
'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) {
membersLi.style.display = 'none';
//membersLi.style.display = 'none';
let user = appUsersManager.getUser(peerID);
if(user.phone && peerID != $rootScope.myID) {
@ -1167,7 +1167,7 @@ export class AppSidebarRight extends SidebarSlider {
//this.log('userFull', userFull);
});
} else {
membersLi.style.display = appPeersManager.isBroadcast(peerID) ? 'none' : '';
//membersLi.style.display = appPeersManager.isBroadcast(peerID) ? 'none' : '';
let chat = appPeersManager.getPeer(peerID);
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 { formatPhoneNumber } from "../../components/misc";
import searchIndexManager from "../searchIndexManager";
import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager";
export type User = {
_: 'user',
@ -45,6 +47,8 @@ export class AppUsersManager {
public contactsList: Set<number> = new Set();
public myID: number;
public getPeersPromise: Promise<number[]>;
constructor() {
apiManager.getUserID().then((id) => {
this.myID = id;
@ -510,19 +514,39 @@ export class AppUsersManager {
});
}
public getTopPeers() {
return apiManager.invokeApi('contacts.getTopPeers', {
flags: 1,
correspondents: true,
offset: 0,
limit: 30,
hash: 0,
}).then((peers: any) => {
//console.log(peers);
this.saveApiUsers(peers.users);
appChatsManager.saveApiChats(peers.chats);
public getTopPeers(): Promise<number[]> {
if(this.getPeersPromise) return this.getPeersPromise;
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,
width?: number,
height?: number,
group?: string
group?: string,
noCache?: true
};
export class RLottiePlayer {
@ -96,13 +97,15 @@ export class RLottiePlayer {
}
}
// проверка на размер уже после скейлинга, сделано для попапа и сайдбарfа, где стикеры 80х80 и 68х68, туда нужно 75%
if(isApple && this.width > 100 && this.height > 100) {
this.cachingDelta = 2; //2 // 50%
} else if(this.width < 100 && this.height < 100) {
this.cachingDelta = Infinity; // 100%
} else {
this.cachingDelta = 4; // 75%
if(!options.noCache) {
// проверка на размер уже после скейлинга, сделано для попапа и сайдбарfа, где стикеры 80х80 и 68х68, туда нужно 75%
if(isApple && this.width > 100 && this.height > 100) {
this.cachingDelta = 2; //2 // 50%
} else if(this.width < 100 && this.height < 100) {
this.cachingDelta = Infinity; // 100%
} else {
this.cachingDelta = 4; // 75%
}
}
// if(isApple) {
@ -284,6 +287,10 @@ export class RLottiePlayer {
} else if(isSafari) {
this.sendQuery('renderFrame', frameNo);
} else {
if(!this.clamped.length) { // fix detached
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
}
this.sendQuery('renderFrame', frameNo, this.clamped);
}
}
@ -471,6 +478,7 @@ class QueryableWorker {
}
}
//console.log('transfer', transfer);
this.worker.postMessage({
'queryMethod': queryMethod,
'queryMethodArguments': args

View File

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

View File

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

View File

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

View File

@ -143,9 +143,9 @@ export function getRichElementValue (node, lines, line, selNode, selOffset) {
export const $rootScope = {
$broadcast: (name/* : string */, detail/*? : any */) => {
/* if(name != 'user_update') {
if(name != 'user_update') {
console.log(dT(), 'Broadcasting ' + name + ' event, with args:', detail);
} */
}
let myCustomEvent = new CustomEvent(name, {detail});
document.dispatchEvent(myCustomEvent);
@ -463,7 +463,7 @@ export function calcImageInBox (imageW, imageH, boxW, boxH, noZooom) {
* @returns {String} The base 16 unicode code.
*/
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];
return pairs.join('-');
}

View File

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

View File

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

View File

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

View File

@ -2,28 +2,53 @@
//display: flex;
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 {
max-height: 100%;
overflow: hidden;
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 {
@ -216,6 +241,11 @@
width: 380px;
margin: 0 auto;
flex: 0 0 auto;
@include respond-to(handhelds) {
width: 100%;
padding: 0 16px;
}
}
.chats-container {
@ -227,6 +257,15 @@
margin-top: 14px;
margin-left: 23px;
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;
line-height: 1.2;
padding-bottom: 1.438rem;
@include respond-to(handhelds) {
padding-right: 24px;
}
}
.sidebar-left-h2 {
@ -314,7 +357,7 @@
.sticker-container {
width: 86px;
height: 86px;
margin: 1px auto 32px;
margin: 1px auto 29px;
flex: 0 0 auto;
}
@ -328,8 +371,9 @@
.sidebar-left-h2 {
color: #707579;
font-size: 15px;
padding-top: 7px;
padding-bottom: 15px;
// padding-top: 7px;
// padding-bottom: 15px;
padding: 7px 16px 15px 16px;
font-weight: 500;
}
}
@ -351,22 +395,27 @@
margin-right: 6px;
}
.folders-container {
padding: 0 16px;
}
// .folders-container {
// padding: 0 16px;
// }
.category {
padding: 7px 0 11px 0;
padding: 7px 16px 11px 16px;
display: flex;
padding-bottom: 11px;
//padding-bottom: 11px;
justify-content: space-between;
cursor: pointer;
position: relative;
margin-bottom: 10px;
p {
height: unset;
}
p:last-child {
color: #707579;
font-size: 14px;
line-height: 1;
line-height: 20px;
}
.btn-primary {
@ -386,7 +435,8 @@
@include respond-to(handhelds) {
.input-wrapper {
width: 328px;
width: 100%;
padding: 0 16px;
}
.input-field input {
@ -410,6 +460,10 @@
.rp {
padding: 8px 3px !important;
height: 48px !important;
@include respond-to(handhelds) {
padding: 8px 12px !important;
}
}
}
@ -429,6 +483,21 @@
.folder-categories {
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 {
@ -440,6 +509,7 @@
user-select: none;
margin-left: 32px;
font-size: 16px;
flex: 1 1 auto;
}
&.blue, &.blue:before {
@ -466,31 +536,64 @@
font-weight: 500;
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 {
li > .rp {
margin: 0 !important;
padding: 7px 12px !important;
height: 62px;
}
.checkbox {
margin-top: 10px;
}
.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: -1px;
[type="checkbox"]+span {
padding-left: 26px;
}
}
}
.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;
transition: opacity .2s ease;
&.thumbnail {
html:not(.is-mac) &.thumbnail {
filter: blur(7px);
}
}

View File

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

View File

@ -2,12 +2,27 @@
max-width: 720px; // 360 + 360 / 2
overflow: hidden;
.btn-primary {
@include respond-to(handhelds) {
height: 50px;
}
}
.subtitle {
margin: 0;
@include respond-to(handhelds) {
font-size: 14px;
}
}
.input-wrapper {
margin-top: 49px;
@include respond-to(handhelds) {
margin-top: 41px;
width: 100%;
padding: 0 16px;
}
}
.tabs-container {
@ -50,6 +65,18 @@
height: auto;
} */
}
.page-password {
.input-wrapper {
@include respond-to(handhelds) {
margin-top: 31px;
}
.btn-primary {
margin-top: 1rem;
}
}
}
}
.page-sign, .page-signUp {
@ -85,6 +112,15 @@
}
}
.page-signQR {
.auth-image {
@include respond-to(handhelds) {
width: 166px;
height: 166px;
}
}
}
/* .page-signQR {
.auth-image {
position: relative;
@ -106,5 +142,16 @@
.auth-image {
margin-top: 10px;
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 {
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: 22px 0 14px;
line-height: 110%;
@include respond-to(handhelds) {
font-size: 20px;
margin: 2px 0 8px;
}
}
input {
@ -766,7 +771,7 @@ avatar-element {
height: 0;
width: 0;
border: solid #bdbdbd;
border: solid #707579;
border-radius: 1px;
border-width: 0 2px 2px 0;
display: inline-block;
@ -805,6 +810,10 @@ avatar-element {
transition: .2s border-color;
position: relative;
z-index: 1;
@include respond-to(handhelds) {
height: 50px;
}
/* font-weight: 500; */
/* &:hover {
@ -869,9 +878,13 @@ avatar-element {
margin: 1.25rem 0;
display: block;
text-align: left;
padding: 0 19px;
padding: 0 18px;
/* font-weight: 500; */
position: relative;
@include respond-to(handhelds) {
margin-bottom: 27px;
}
}
[type="checkbox"] {
@ -1036,6 +1049,11 @@ input:focus, button:focus {
width: 166px;
height: 166px;
margin: 0 auto 18px;
@include respond-to(handhelds) {
width: 120px;
height: 120px;
}
}
/* .phone-wrapper {
@ -1053,6 +1071,10 @@ input:focus, button:focus {
cursor: pointer;
font-size: 1.5rem;
@include respond-to(handhelds) {
margin-top: -14px;
}
html.no-touch &:hover {
opacity: 1;
}