Browse Source

fixes and fixes

master
morethanwords 4 years ago
parent
commit
d3836e71f8
  1. 3
      .gitignore
  2. 35
      src/components/appSearch.ts
  3. 2
      src/components/appSelectPeers.ts
  4. 8
      src/components/audio.ts
  5. 17
      src/components/chatInput.ts
  6. 131
      src/components/emoticonsDropdown.ts
  7. 8
      src/components/preloader.ts
  8. 2
      src/components/sidebarLeft/chatFolders.ts
  9. 5
      src/components/sidebarLeft/settings.ts
  10. 2
      src/emoji.json
  11. 2
      src/format_jsons.js
  12. 16
      src/index.hbs
  13. 272
      src/lib/MP4Source.js
  14. 2
      src/lib/MP4Sourcee.ts
  15. 279
      src/lib/MP4Sourceee.ts
  16. 72
      src/lib/appManagers/appDialogsManager.ts
  17. 6
      src/lib/appManagers/appDocsManager.ts
  18. 35
      src/lib/appManagers/appImManager.ts
  19. 5
      src/lib/appManagers/appMediaViewer.ts
  20. 237
      src/lib/appManagers/appMessagesManager.ts
  21. 102
      src/lib/appManagers/appSidebarLeft.ts
  22. 10
      src/lib/appManagers/appSidebarRight.ts
  23. 195
      src/lib/appManagers/appStateManager.ts
  24. 48
      src/lib/appManagers/appUsersManager.ts
  25. 2
      src/lib/config.ts
  26. 24
      src/lib/lottieLoader.ts
  27. 1
      src/lib/mtproto/networker.ts
  28. 16
      src/lib/mtproto/transports/websocket.ts
  29. 4
      src/lib/richtextprocessor.js
  30. 6
      src/lib/utils.js
  31. 10
      src/pages/pageAuthCode.ts
  32. 29
      src/pages/pagePassword.ts
  33. 2
      src/scss/partials/_chatlist.scss
  34. 189
      src/scss/partials/_leftSidebar.scss
  35. 2
      src/scss/partials/_rightSidebar.scss
  36. 4
      src/scss/partials/_selector.scss
  37. 47
      src/scss/partials/pages/_pages.scss
  38. 6
      src/scss/partials/popups/_mediaAttacher.scss
  39. 26
      src/scss/style.scss

3
.gitignore vendored

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

35
src/components/appSearch.ts

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

2
src/components/appSelectPeers.ts

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

8
src/components/audio.ts

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

17
src/components/chatInput.ts

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

131
src/components/emoticonsDropdown.ts

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

8
src/components/preloader.ts

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

2
src/components/sidebarLeft/chatFolders.ts

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

5
src/components/sidebarLeft/settings.ts

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

2
src/emoji.json

File diff suppressed because one or more lines are too long

2
src/format_jsons.js

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

16
src/index.hbs

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

272
src/lib/MP4Source.js

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

2
src/lib/MP4Sourcee.ts

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

279
src/lib/MP4Sourceee.ts

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

72
src/lib/appManagers/appDialogsManager.ts

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

6
src/lib/appManagers/appDocsManager.ts

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

35
src/lib/appManagers/appImManager.ts

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

5
src/lib/appManagers/appMediaViewer.ts

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

237
src/lib/appManagers/appMessagesManager.ts

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

102
src/lib/appManagers/appSidebarLeft.ts

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

10
src/lib/appManagers/appSidebarRight.ts

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

195
src/lib/appManagers/appStateManager.ts

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

48
src/lib/appManagers/appUsersManager.ts

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

2
src/lib/config.ts

File diff suppressed because one or more lines are too long

24
src/lib/lottieLoader.ts

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

1
src/lib/mtproto/networker.ts

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

16
src/lib/mtproto/transports/websocket.ts

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

4
src/lib/richtextprocessor.js

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

6
src/lib/utils.js

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

10
src/pages/pageAuthCode.ts

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

29
src/pages/pagePassword.ts

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

2
src/scss/partials/_chatlist.scss

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

189
src/scss/partials/_leftSidebar.scss

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

2
src/scss/partials/_rightSidebar.scss

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

4
src/scss/partials/_selector.scss

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

47
src/scss/partials/pages/_pages.scss

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

6
src/scss/partials/popups/_mediaAttacher.scss

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

26
src/scss/style.scss

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

Loading…
Cancel
Save