fixes and fixes
This commit is contained in:
parent
b2ef3c8bda
commit
257d334e77
|
@ -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.
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
@ -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];
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
} */
|
} */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -321,6 +321,7 @@ class MTPNetworker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// тут можно сделать таймаут и выводить дисконнект
|
||||||
public pushMessage(message: {
|
public pushMessage(message: {
|
||||||
msg_id: string,
|
msg_id: string,
|
||||||
seq_no: number,
|
seq_no: number,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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('-');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue