Browse Source

Wrap pinned photo

Video render photo, no video
Fix iOS Safari scroll
Stickers sizes for devicePixelRatio
master
morethanwords 5 years ago
parent
commit
cd4b8db2d1
  1. 3
      src/components/chatInput.ts
  2. 2
      src/components/emoticonsDropdown.ts
  3. 2
      src/components/lazyLoadQueue.ts
  4. 4
      src/components/preloader.ts
  5. 49
      src/components/wrappers.ts
  6. 7
      src/index.hbs
  7. 37
      src/lib/appManagers/apiUpdatesManager.ts
  8. 5
      src/lib/appManagers/appDialogsManager.ts
  9. 88
      src/lib/appManagers/appImManager.ts
  10. 2
      src/lib/appManagers/appMediaViewer.ts
  11. 10
      src/lib/appManagers/appMessagesManager.ts
  12. 6
      src/lib/appManagers/appWebpManager.ts
  13. 6
      src/lib/cacheStorage.ts
  14. 5
      src/lib/config.ts
  15. 103
      src/lib/lottieLoader.ts
  16. 17
      src/lib/polyfill.ts
  17. 2
      src/scss/partials/_chat.scss
  18. 5
      src/scss/style.scss

3
src/components/chatInput.ts

@ -92,7 +92,8 @@ export class ChatInput {
encoderSampleRate: 48000, encoderSampleRate: 48000,
monitorGain: 0, monitorGain: 0,
numberOfChannels: 1, numberOfChannels: 1,
recordingGain: 1 recordingGain: 1,
reuseWorker: true
}); });
} catch(err) { } catch(err) {
this.btnSend.classList.remove('tgico-microphone2'); this.btnSend.classList.remove('tgico-microphone2');

2
src/components/emoticonsDropdown.ts

@ -345,7 +345,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let prevCategoryIndex = 0; let prevCategoryIndex = 0;
let stickersScroll = new Scrollable(contentStickersDiv, 'y', 'STICKERS', undefined, undefined, 2); let stickersScroll = new Scrollable(contentStickersDiv, 'y', 'STICKERS', undefined, undefined, 2);
stickersScroll.container.addEventListener('scroll', (e) => { stickersScroll.container.addEventListener('scroll', (e) => {
lottieLoader.checkAnimations(); lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll); prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, stickersScroll.container, menuScroll);
}); });

2
src/components/lazyLoadQueue.ts

@ -13,7 +13,7 @@ export default class LazyLoadQueue {
private unlockResolve: () => void = null; private unlockResolve: () => void = null;
private log = console.log.bind(console, '[LL]:'); private log = console.log.bind(console, '[LL]:');
private debug = true; private debug = false;
private observer: IntersectionObserver; private observer: IntersectionObserver;

4
src/components/preloader.ts

@ -61,7 +61,7 @@ export default class ProgressivePreloader {
promise.notify = (details: {done: number, total: number}) => { promise.notify = (details: {done: number, total: number}) => {
if(tempID != this.tempID) return; if(tempID != this.tempID) return;
console.log('preloader download', promise, details); //console.log('preloader download', promise, details);
let percents = details.done / details.total * 100; let percents = details.done / details.total * 100;
this.setProgress(percents); this.setProgress(percents);
}; };
@ -116,7 +116,7 @@ export default class ProgressivePreloader {
} }
let totalLength = this.circle.getTotalLength(); let totalLength = this.circle.getTotalLength();
console.log('setProgress', (percents / 100 * totalLength)); //console.log('setProgress', (percents / 100 * totalLength));
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200'; this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200';
} }
} }

49
src/components/wrappers.ts

@ -2,12 +2,10 @@ import appPhotosManager from '../lib/appManagers/appPhotosManager';
//import CryptoWorker from '../lib/crypto/cryptoworker'; //import CryptoWorker from '../lib/crypto/cryptoworker';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import LottieLoader from '../lib/lottieLoader'; import LottieLoader from '../lib/lottieLoader';
import appStickersManager from "../lib/appManagers/appStickersManager";
import appDocsManager from "../lib/appManagers/appDocsManager"; import appDocsManager from "../lib/appManagers/appDocsManager";
import { formatBytes, getEmojiToneIndex } from "../lib/utils"; import { formatBytes, getEmojiToneIndex } from "../lib/utils";
import ProgressivePreloader from './preloader'; import ProgressivePreloader from './preloader';
import LazyLoadQueue from './lazyLoadQueue'; import LazyLoadQueue from './lazyLoadQueue';
import apiFileManager from '../lib/mtproto/apiFileManager';
import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer'; import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer';
import { RichTextProcessor } from '../lib/richtextprocessor'; import { RichTextProcessor } from '../lib/richtextprocessor';
import { CancellablePromise } from '../lib/polyfill'; import { CancellablePromise } from '../lib/polyfill';
@ -30,6 +28,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
middleware: () => boolean, middleware: () => boolean,
lazyLoadQueue: LazyLoadQueue lazyLoadQueue: LazyLoadQueue
}) { }) {
if(doc.type == 'video') {
return wrapPhoto(doc, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware);
}
let img: HTMLImageElement; let img: HTMLImageElement;
if(withTail) { if(withTail) {
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut);
@ -95,17 +97,19 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
return; return;
} }
console.log('loaded doc:', doc, doc.url, container); //console.log('loaded doc:', doc, doc.url, container);
renderImageFromUrl(source, doc.url); renderImageFromUrl(source, doc.url);
source.type = doc.mime_type; source.type = doc.mime_type;
video.append(source); video.append(source);
video.setAttribute('playsinline', '');
if(img && img.parentElement) { if(img && img.parentElement) {
img.remove(); img.remove();
} }
if(doc.type == 'gif') { if(doc.type == 'gif') {
video.muted = true;
video.autoplay = true; video.autoplay = true;
video.loop = true; video.loop = true;
video.play(); video.play();
@ -687,7 +691,7 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string},
return img; return img;
} }
export function wrapPhoto(photoID: string, message: any, container: HTMLDivElement, boxWidth = mediaSizes.active.regular.width, boxHeight = mediaSizes.active.regular.height, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean, size: MTPhotoSize = null) { export function wrapPhoto(photoID: any, message: any, container: HTMLDivElement, boxWidth = mediaSizes.active.regular.width, boxHeight = mediaSizes.active.regular.height, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean, size: MTPhotoSize = null) {
let photo = appPhotosManager.getPhoto(photoID); let photo = appPhotosManager.getPhoto(photoID);
let image: HTMLImageElement; let image: HTMLImageElement;
@ -734,7 +738,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme
return promise.then(() => { return promise.then(() => {
if(middleware && !middleware()) return; if(middleware && !middleware()) return;
renderImageFromUrl(image || container, photo.url); renderImageFromUrl(image || container, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
}); });
}; };
@ -865,8 +869,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
loop: !emoji, loop: !emoji,
autoplay: true, autoplay: true,
animationData: JSON.parse(json), animationData: JSON.parse(json),
width: !emoji ? 200 : 140, width: !emoji ? 200 : undefined,
height: !emoji ? 200 : 140 height: !emoji ? 200 : undefined
}, group, toneIndex); }, group, toneIndex);
animation.addListener('ready', () => { animation.addListener('ready', () => {
@ -932,31 +936,34 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat' && stickerType != 2}), Promise.resolve()) : load(); return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat' && stickerType != 2}), Promise.resolve()) : load();
} }
export function wrapReply(title: string, subtitle: string, message?: any) { export function wrapReply(title: string, subtitle: string, message?: any, isPinned?: boolean) {
let div = document.createElement('div'); const prefix = isPinned ? 'pinned-message' : 'reply';
div.classList.add('reply'); const div = document.createElement('div');
div.classList.add(prefix);
let replyBorder = document.createElement('div'); const replyBorder = document.createElement('div');
replyBorder.classList.add('reply-border'); replyBorder.classList.add(prefix + '-border');
let replyContent = document.createElement('div'); const replyContent = document.createElement('div');
replyContent.classList.add('reply-content'); replyContent.classList.add(prefix + '-content');
let replyTitle = document.createElement('div'); const replyTitle = document.createElement('div');
replyTitle.classList.add('reply-title'); replyTitle.classList.add(prefix + '-title');
let replySubtitle = document.createElement('div'); const replySubtitle = document.createElement('div');
replySubtitle.classList.add('reply-subtitle'); replySubtitle.classList.add(prefix + '-subtitle');
replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : '';
let media = message && message.media; const media = message && message.media;
if(media) { if(media) {
replySubtitle.innerHTML = message.rReply; replySubtitle.innerHTML = message.rReply;
console.log('wrap reply', media);
if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) {
let replyMedia = document.createElement('div'); let replyMedia = document.createElement('div');
replyMedia.classList.add('reply-media'); replyMedia.classList.add(prefix + '-media');
let photo = media.photo || media.document; let photo = media.photo || media.document;
@ -971,7 +978,7 @@ export function wrapReply(title: string, subtitle: string, message?: any) {
}); });
replyContent.append(replyMedia); replyContent.append(replyMedia);
div.classList.add('is-reply-media'); div.classList.add('is-media');
} }
} else { } else {
replySubtitle.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; replySubtitle.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';

7
src/index.hbs

@ -394,13 +394,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="pinned-message">
<div class="pinned-message-border"></div>
<div class="pinned-message-content">
<div class="pinned-message-title">Pinned Message</div>
<div class="pinned-message-subtitle"></div>
</div>
</div>
<div class="btn-icon rp chat-mute-button" style="display: none;"></div> <div class="btn-icon rp chat-mute-button" style="display: none;"></div>
<div class="btn-icon rp tgico-search chat-search-button"></div> <div class="btn-icon rp tgico-search chat-search-button"></div>
<div class="btn-icon btn-menu-toggle rp tgico-more chat-more-button"> <div class="btn-icon btn-menu-toggle rp tgico-more chat-more-button">

37
src/lib/appManagers/apiUpdatesManager.ts

@ -5,6 +5,7 @@ import { dT, $rootScope, tsNow } from "../utils";
import appPeersManager from "./appPeersManager"; import appPeersManager from "./appPeersManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import { logger, LogLevels } from '../polyfill';
export class ApiUpdatesManager { export class ApiUpdatesManager {
public updatesState: { public updatesState: {
@ -26,6 +27,8 @@ export class ApiUpdatesManager {
public channelStates: any = {}; public channelStates: any = {};
private attached = false; private attached = false;
private log = logger('UPDATES', LogLevels.error);
public popPendingSeqUpdate() { public popPendingSeqUpdate() {
var nextSeq = this.updatesState.seq + 1; var nextSeq = this.updatesState.seq + 1;
var pendingUpdatesData = this.updatesState.pendingSeqUpdates[nextSeq]; var pendingUpdatesData = this.updatesState.pendingSeqUpdates[nextSeq];
@ -67,7 +70,7 @@ export class ApiUpdatesManager {
curState.pendingPtsUpdates.sort((a: any, b: any) => { curState.pendingPtsUpdates.sort((a: any, b: any) => {
return a.pts - b.pts; return a.pts - b.pts;
}); });
// console.log(dT(), 'pop update', channelID, curState.pendingPtsUpdates) // this.log(dT(), 'pop update', channelID, curState.pendingPtsUpdates)
var curPts = curState.pts; var curPts = curState.pts;
var goodPts = false; var goodPts = false;
@ -86,7 +89,7 @@ export class ApiUpdatesManager {
return false; return false;
} }
console.log(dT(), 'pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1)); this.log(dT(), 'pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
curState.pts = goodPts; curState.pts = goodPts;
for(i = 0; i <= goodIndex; i++) { for(i = 0; i <= goodIndex; i++) {
@ -170,12 +173,12 @@ export class ApiUpdatesManager {
break; break;
default: default:
console.warn(dT(), 'Unknown update message', updateMessage); this.log.warn(dT(), 'Unknown update message', updateMessage);
} }
} }
public getDifference() { public getDifference() {
// console.trace(dT(), 'Get full diff') // this.trace(dT(), 'Get full diff')
const updatesState = this.updatesState; const updatesState = this.updatesState;
if(!updatesState.syncLoading) { if(!updatesState.syncLoading) {
updatesState.syncLoading = true; updatesState.syncLoading = true;
@ -196,7 +199,7 @@ export class ApiUpdatesManager {
timeout: 0x7fffffff timeout: 0x7fffffff
}).then((differenceResult: any) => { }).then((differenceResult: any) => {
if(differenceResult._ == 'updates.differenceEmpty') { if(differenceResult._ == 'updates.differenceEmpty') {
console.log(dT(), 'apply empty diff', differenceResult.seq); this.log(dT(), 'apply empty diff', differenceResult.seq);
updatesState.date = differenceResult.date; updatesState.date = differenceResult.date;
updatesState.seq = differenceResult.seq; updatesState.seq = differenceResult.seq;
updatesState.syncLoading = false; updatesState.syncLoading = false;
@ -208,7 +211,7 @@ export class ApiUpdatesManager {
appChatsManager.saveApiChats(differenceResult.chats); appChatsManager.saveApiChats(differenceResult.chats);
// Should be first because of updateMessageID // Should be first because of updateMessageID
// console.log(dT(), 'applying', differenceResult.other_updates.length, 'other updates') // this.log(dT(), 'applying', differenceResult.other_updates.length, 'other updates')
differenceResult.other_updates.forEach((update: any) => { differenceResult.other_updates.forEach((update: any) => {
switch(update._) { switch(update._) {
@ -222,7 +225,7 @@ export class ApiUpdatesManager {
this.saveUpdate(update); this.saveUpdate(update);
}); });
// console.log(dT(), 'applying', differenceResult.new_messages.length, 'new messages') // this.log(dT(), 'applying', differenceResult.new_messages.length, 'new messages')
differenceResult.new_messages.forEach((apiMessage: any) => { differenceResult.new_messages.forEach((apiMessage: any) => {
this.saveUpdate({ this.saveUpdate({
_: 'updateNewMessage', _: 'updateNewMessage',
@ -237,12 +240,12 @@ export class ApiUpdatesManager {
updatesState.pts = nextState.pts; updatesState.pts = nextState.pts;
updatesState.date = nextState.date; updatesState.date = nextState.date;
// console.log(dT(), 'apply diff', updatesState.seq, updatesState.pts) // this.log(dT(), 'apply diff', updatesState.seq, updatesState.pts)
if(differenceResult._ == 'updates.differenceSlice') { if(differenceResult._ == 'updates.differenceSlice') {
this.getDifference(); this.getDifference();
} else { } else {
// console.log(dT(), 'finished get diff') // this.log(dT(), 'finished get diff')
$rootScope.$broadcast('stateSynchronized'); $rootScope.$broadcast('stateSynchronized');
updatesState.syncLoading = false; updatesState.syncLoading = false;
} }
@ -261,25 +264,25 @@ export class ApiUpdatesManager {
clearTimeout(channelState.syncPending.timeout); clearTimeout(channelState.syncPending.timeout);
channelState.syncPending = false; channelState.syncPending = false;
} }
// console.log(dT(), 'Get channel diff', appChatsManager.getChat(channelID), channelState.pts) // this.log(dT(), 'Get channel diff', appChatsManager.getChat(channelID), channelState.pts)
apiManager.invokeApi('updates.getChannelDifference', { apiManager.invokeApi('updates.getChannelDifference', {
channel: appChatsManager.getChannelInput(channelID), channel: appChatsManager.getChannelInput(channelID),
filter: {_: 'channelMessagesFilterEmpty'}, filter: {_: 'channelMessagesFilterEmpty'},
pts: channelState.pts, pts: channelState.pts,
limit: 30 limit: 30
}, {timeout: 0x7fffffff}).then((differenceResult: any) => { }, {timeout: 0x7fffffff}).then((differenceResult: any) => {
// console.log(dT(), 'channel diff result', differenceResult) // this.log(dT(), 'channel diff result', differenceResult)
channelState.pts = differenceResult.pts; channelState.pts = differenceResult.pts;
if (differenceResult._ == 'updates.channelDifferenceEmpty') { if (differenceResult._ == 'updates.channelDifferenceEmpty') {
console.log(dT(), 'apply channel empty diff', differenceResult); this.log(dT(), 'apply channel empty diff', differenceResult);
channelState.syncLoading = false; channelState.syncLoading = false;
$rootScope.$broadcast('stateSynchronized'); $rootScope.$broadcast('stateSynchronized');
return false; return false;
} }
if(differenceResult._ == 'updates.channelDifferenceTooLong') { if(differenceResult._ == 'updates.channelDifferenceTooLong') {
console.log(dT(), 'channel diff too long', differenceResult); this.log(dT(), 'channel diff too long', differenceResult);
channelState.syncLoading = false; channelState.syncLoading = false;
delete this.channelStates[channelID]; delete this.channelStates[channelID];
this.saveUpdate({_: 'updateChannelReload', channel_id: channelID}); this.saveUpdate({_: 'updateChannelReload', channel_id: channelID});
@ -290,12 +293,12 @@ export class ApiUpdatesManager {
appChatsManager.saveApiChats(differenceResult.chats); appChatsManager.saveApiChats(differenceResult.chats);
// Should be first because of updateMessageID // Should be first because of updateMessageID
console.log(dT(), 'applying', differenceResult.other_updates.length, 'channel other updates') this.log(dT(), 'applying', differenceResult.other_updates.length, 'channel other updates')
differenceResult.other_updates.forEach((update: any) => { differenceResult.other_updates.forEach((update: any) => {
this.saveUpdate(update); this.saveUpdate(update);
}); });
console.log(dT(), 'applying', differenceResult.new_messages.length, 'channel new messages') this.log(dT(), 'applying', differenceResult.new_messages.length, 'channel new messages')
differenceResult.new_messages.forEach((apiMessage: any) => { differenceResult.new_messages.forEach((apiMessage: any) => {
this.saveUpdate({ this.saveUpdate({
_: 'updateNewChannelMessage', _: 'updateNewChannelMessage',
@ -305,13 +308,13 @@ export class ApiUpdatesManager {
}); });
}); });
console.log(dT(), 'apply channel diff', channelState.pts); this.log(dT(), 'apply channel diff', channelState.pts);
if(differenceResult._ == 'updates.channelDifference' && if(differenceResult._ == 'updates.channelDifference' &&
!differenceResult.pFlags['final']) { !differenceResult.pFlags['final']) {
this.getChannelDifference(channelID); this.getChannelDifference(channelID);
} else { } else {
console.log(dT(), 'finished channel get diff'); this.log(dT(), 'finished channel get diff');
$rootScope.$broadcast('stateSynchronized'); $rootScope.$broadcast('stateSynchronized');
channelState.syncLoading = false; channelState.syncLoading = false;
} }

5
src/lib/appManagers/appDialogsManager.ts

@ -741,6 +741,11 @@ export class AppDialogsManager {
public setUnreadMessages(dialog: Dialog) { public setUnreadMessages(dialog: Dialog) {
let dom = this.getDialogDom(dialog.peerID); let dom = this.getDialogDom(dialog.peerID);
if(!dom) {
this.log.error('setUnreadMessages no dom!', dialog);
return;
}
let lastMessage = appMessagesManager.getMessage(dialog.top_message); let lastMessage = appMessagesManager.getMessage(dialog.top_message);
if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted && if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted &&
lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID && lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID &&

88
src/lib/appManagers/appImManager.ts

@ -1,6 +1,6 @@
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild } from "../utils"; import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild, cancelEvent } from "../utils";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appMessagesManager, { Dialog } from "./appMessagesManager"; import appMessagesManager, { Dialog } from "./appMessagesManager";
import appPeersManager from "./appPeersManager"; import appPeersManager from "./appPeersManager";
@ -36,7 +36,7 @@ console.log('appImManager included33!');
appSidebarLeft; // just to include appSidebarLeft; // just to include
const testScroll = true; const testScroll = false;
const IGNOREACTIONS = ['messageActionChannelMigrateFrom']; const IGNOREACTIONS = ['messageActionChannelMigrateFrom'];
@ -237,8 +237,7 @@ export class AppImManager {
public updateStatusInterval = 0; public updateStatusInterval = 0;
public pinnedMsgID = 0; public pinnedMsgID = 0;
private pinnedMessageContainer = this.columnEl.querySelector('.pinned-message') as HTMLDivElement; private pinnedMessageContainer: HTMLDivElement = null;
private pinnedMessageContent = this.pinnedMessageContainer.querySelector('.pinned-message-subtitle') as HTMLDivElement;
public lazyLoadQueue = new LazyLoadQueue(); public lazyLoadQueue = new LazyLoadQueue();
@ -496,8 +495,17 @@ export class AppImManager {
}, {once: true}); }, {once: true});
}); });
(this.columnEl.querySelector('.person') as HTMLDivElement).addEventListener('click', () => { this.topbar.addEventListener('click', (e) => {
const pinned = findUpClassName(e.target, 'pinned-message');
if(pinned) {
e.preventDefault();
e.cancelBubble = true;
let mid = +pinned.dataset.mid;
this.setPeer(this.peerID, mid);
} else {
appSidebarRight.toggleSidebar(true); appSidebarRight.toggleSidebar(true);
}
}); });
this.bubblesContainer.addEventListener('click', (e) => { this.bubblesContainer.addEventListener('click', (e) => {
@ -630,14 +638,6 @@ export class AppImManager {
} }
}); });
this.pinnedMessageContainer.addEventListener('click', (e) => {
e.preventDefault();
e.cancelBubble = true;
let mid = +this.pinnedMessageContainer.getAttribute('data-mid');
this.setPeer(this.peerID, mid);
});
[this.btnMute, this.menuButtons.mute].forEach(el => { [this.btnMute, this.menuButtons.mute].forEach(el => {
el.addEventListener('click', () => this.mutePeer(this.peerID)); el.addEventListener('click', () => this.mutePeer(this.peerID));
}); });
@ -757,10 +757,21 @@ export class AppImManager {
public setPinnedMessage(message: any) { public setPinnedMessage(message: any) {
/////this.log('setting pinned message', message); /////this.log('setting pinned message', message);
return; //return;
this.pinnedMessageContainer.dataset.mid = '' + message.mid; const scrollTop = this.scrollable.container.scrollTop;
const newPinned = wrapReply('Pinned Message', message.message, message, true);
newPinned.dataset.mid = '' + message.mid;
this.topbar.insertBefore(newPinned, this.btnMute);
this.topbar.classList.add('is-pinned-shown'); this.topbar.classList.add('is-pinned-shown');
this.pinnedMessageContent.innerHTML = message.rReply;
if(this.pinnedMessageContainer) {
this.pinnedMessageContainer.remove();
}
this.pinnedMessageContainer = newPinned;
//this.pinnedMessageContent.innerHTML = message.rReply;
this.scrollable.scrollTop = scrollTop + 60;
} }
public updateStatus() { public updateStatus() {
@ -784,7 +795,7 @@ export class AppImManager {
} }
public loadMoreHistory(top: boolean) { public loadMoreHistory(top: boolean) {
this.log('loadMoreHistory', top); //this.log('loadMoreHistory', top);
if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return; if(!this.peerID || testScroll || this.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return;
// warning, если иды только отрицательные то вниз не попадёт (хотя мб и так не попадёт) // warning, если иды только отрицательные то вниз не попадёт (хотя мб и так не попадёт)
@ -811,13 +822,13 @@ export class AppImManager {
} }
} }
public onScroll() { public onScroll(e: Event) {
if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF); if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF);
//if(this.scrollable.scrollLocked) return; //if(this.scrollable.scrollLocked) return;
this.onScrollRAF = window.requestAnimationFrame(() => { this.onScrollRAF = window.requestAnimationFrame(() => {
lottieLoader.checkAnimations(false, 'chat'); //lottieLoader.checkAnimations(false, 'chat');
if(!touchSupport) { if(!touchSupport) {
if(this.isScrollingTimeout) { if(this.isScrollingTimeout) {
@ -1076,7 +1087,11 @@ export class AppImManager {
if(!cached) { if(!cached) {
this.scrollable.container.innerHTML = ''; this.scrollable.container.innerHTML = '';
//oldChatInner.remove(); //oldChatInner.remove();
!samePeer && this.finishPeerChange();
if(!samePeer) {
this.finishPeerChange();
}
this.preloader.attach(this.bubblesContainer); this.preloader.attach(this.bubblesContainer);
if(mediaSizes.isMobile) { if(mediaSizes.isMobile) {
@ -1093,11 +1108,9 @@ export class AppImManager {
if(cached) { if(cached) {
this.scrollable.container.innerHTML = ''; this.scrollable.container.innerHTML = '';
//oldChatInner.remove(); //oldChatInner.remove();
!samePeer && this.finishPeerChange();
const pinned = appMessagesManager.getPinnedMessage(peerID); if(!samePeer) {
if(pinned && !pinned.deleted) { this.finishPeerChange();
this.setPinnedMessage(pinned);
} }
if(mediaSizes.isMobile) { if(mediaSizes.isMobile) {
@ -1206,6 +1219,14 @@ export class AppImManager {
this.btnMute.style.display = appPeersManager.isBroadcast(peerID) ? '' : 'none'; this.btnMute.style.display = appPeersManager.isBroadcast(peerID) ? '' : 'none';
this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : ''; this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : '';
const pinned = appMessagesManager.getPinnedMessage(peerID);
if(pinned && !pinned.deleted) {
this.setPinnedMessage(pinned);
} else if(this.pinnedMessageContainer) {
this.pinnedMessageContainer.remove();
this.pinnedMessageContainer = null;
}
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
let title = ''; let title = '';
if(this.peerID == this.myID) title = 'Saved Messages'; if(this.peerID == this.myID) title = 'Saved Messages';
@ -1258,7 +1279,7 @@ export class AppImManager {
//bubble.remove(); //bubble.remove();
}); });
lottieLoader.checkAnimations(); lottieLoader.checkAnimations(false, 'chat');
this.deleteEmptyDateGroups(); this.deleteEmptyDateGroups();
} }
@ -1366,7 +1387,7 @@ export class AppImManager {
if(el instanceof HTMLVideoElement) { if(el instanceof HTMLVideoElement) {
let source = el.firstElementChild as HTMLSourceElement; let source = el.firstElementChild as HTMLSourceElement;
if(!source || !source.src) { if(!source || !source.src) {
this.log.warn('no source', el, source, 'src', source.src); //this.log.warn('no source', el, source, 'src', source.src);
return; return;
} else if(el.readyState >= 4) return; } else if(el.readyState >= 4) return;
} else if(el.complete || !el.src) return; } else if(el.complete || !el.src) return;
@ -1381,8 +1402,8 @@ export class AppImManager {
}; };
if(el instanceof HTMLVideoElement) { if(el instanceof HTMLVideoElement) {
el.addEventListener('loadeddata', onLoad); el.addEventListener('canplay', onLoad);
r = () => el.readyState >= 4; r = () => el.readyState >= 1;
} else { } else {
el.addEventListener('load', onLoad); el.addEventListener('load', onLoad);
r = () => el.complete; r = () => el.complete;
@ -1393,8 +1414,9 @@ export class AppImManager {
window.requestAnimationFrame(c); window.requestAnimationFrame(c);
let timeout = setTimeout(() => { let timeout = setTimeout(() => {
console.log('did not called', el, el.parentElement, el.complete, src); // @ts-ignore
reject(); this.log.error('did not called', el, el.parentElement, el.complete, el.readyState, src);
resolve();
}, 1500); }, 1500);
}); });
@ -2242,6 +2264,8 @@ export class AppImManager {
let scrollTop = this.scrollable.scrollTop; let scrollTop = this.scrollable.scrollTop;
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
//this.chatInner.style.height = '100%';
//previousScrollHeightMinusTop = 0;
/* if(reverse) { /* if(reverse) {
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
} else { } else {
@ -2262,7 +2286,11 @@ export class AppImManager {
if(previousScrollHeightMinusTop !== undefined) { if(previousScrollHeightMinusTop !== undefined) {
const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop; const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight); this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight);
// touchSupport for safari iOS
touchSupport && (this.scrollable.container.style.overflow = 'hidden');
this.scrollable.scrollTop = newScrollTop; this.scrollable.scrollTop = newScrollTop;
touchSupport && (this.scrollable.container.style.overflow = '');
} }
resolve(true); resolve(true);

2
src/lib/appManagers/appMediaViewer.ts

@ -777,7 +777,9 @@ export class AppMediaViewer {
let video = mover.querySelector('video') || document.createElement('video'); let video = mover.querySelector('video') || document.createElement('video');
let source = video.firstElementChild as HTMLSourceElement || document.createElement('source'); let source = video.firstElementChild as HTMLSourceElement || document.createElement('source');
video.setAttribute('playsinline', '');
if(media.type == 'gif') { if(media.type == 'gif') {
video.muted = true;
video.autoplay = true; video.autoplay = true;
video.loop = true; video.loop = true;
} }

10
src/lib/appManagers/appMessagesManager.ts

@ -23,7 +23,7 @@ import appPollsManager from "./appPollsManager";
import searchIndexManager from '../searchIndexManager'; import searchIndexManager from '../searchIndexManager';
import { MTDocument, MTPhotoSize } from "../../types"; import { MTDocument, MTPhotoSize } from "../../types";
console.trace('include'); //console.trace('include');
const APITIMEOUT = 0; const APITIMEOUT = 0;
@ -238,10 +238,10 @@ export class AppMessagesManager {
apiUpdatesManager.attach(updates ?? null); apiUpdatesManager.attach(updates ?? null);
resolve(); resolve();
}).catch(resolve); }).catch(resolve).finally(() => {
});
setInterval(() => this.saveState(), 10000); setInterval(() => this.saveState(), 10000);
});
});
} }
public saveState() { public saveState() {
@ -1986,7 +1986,7 @@ export class AppMessagesManager {
str = langPack[_] + suffix; str = langPack[_] + suffix;
} }
console.log('message action:', action); //console.log('message action:', action);
messageText = '<i>' + str + '</i>'; messageText = '<i>' + str + '</i>';
} }

6
src/lib/appManagers/appWebpManager.ts

@ -19,7 +19,7 @@ class AppWebpManager {
if(!res) { if(!res) {
(window as any).webpLoaded = () => { (window as any).webpLoaded = () => {
console.log('webpHero loaded'); //console.log('webpHero loaded');
this.webpMachine = new (window as any).WebpMachine(); this.webpMachine = new (window as any).WebpMachine();
resolve(); resolve();
}; };
@ -56,7 +56,7 @@ class AppWebpManager {
this.busyPromise = this.convert(bytes); this.busyPromise = this.convert(bytes);
let res = await this.busyPromise; let res = await this.busyPromise;
console.log('converted webp', res); //console.log('converted webp', res);
callback(res as Uint8Array); callback(res as Uint8Array);
@ -85,7 +85,7 @@ class AppWebpManager {
} }
public convertToPng(bytes: Uint8Array) { public convertToPng(bytes: Uint8Array) {
console.warn('convertToPng!'); //console.warn('convertToPng!');
return new Promise<Uint8Array>((resolve, reject) => { return new Promise<Uint8Array>((resolve, reject) => {
// @ts-ignore // @ts-ignore
this.queue.push({bytes, callback: resolve}); this.queue.push({bytes, callback: resolve});

6
src/lib/cacheStorage.ts

@ -1,12 +1,12 @@
import {blobConstruct, bytesToBase64, blobSafeMimeType, dataUrlToBlob} from './bin_utils'; import {blobConstruct} from './bin_utils';
import FileManager from './filemanager'; import FileManager from './filemanager';
import { logger } from './polyfill'; //import { logger } from './polyfill';
class CacheStorageController { class CacheStorageController {
public dbName = 'cachedFiles'; public dbName = 'cachedFiles';
public openDbPromise: Promise<Cache>; public openDbPromise: Promise<Cache>;
private log: ReturnType<typeof logger> = logger('CS'); //private log: ReturnType<typeof logger> = logger('CS');
constructor() { constructor() {
this.openDatabase(); this.openDatabase();

5
src/lib/config.ts

@ -92,13 +92,16 @@ export const mediaSizes = new MediaSizes();
// @ts-ignore // @ts-ignore
export const touchSupport = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch); export const touchSupport = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch);
export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) != -1;
const Config = { const Config = {
Emoji, Emoji,
LatinizeMap, LatinizeMap,
TLD, TLD,
Countries, Countries,
MediaSizes: mediaSizes, MediaSizes: mediaSizes,
touchSupport touchSupport,
isApple
}; };
(window as any).Config = Config; (window as any).Config = Config;
export default Config; export default Config;

103
src/lib/lottieLoader.ts

@ -1,10 +1,19 @@
import { isInDOM } from "./utils"; import { isInDOM } from "./utils";
import { isApple } from "./config";
let convert = (value: number) => { let convert = (value: number) => {
return Math.round(Math.min(Math.max(value, 0), 1) * 255); return Math.round(Math.min(Math.max(value, 0), 1) * 255);
}; };
type RLottiePlayerListeners = 'enterFrame' | 'ready'; type RLottiePlayerListeners = 'enterFrame' | 'ready';
type RLottieOptions = {
container: HTMLElement,
autoplay?: boolean,
animationData: any,
loop?: boolean,
width?: number,
height?: number
};
export class RLottiePlayer { export class RLottiePlayer {
public static reqId = 0; public static reqId = 0;
@ -16,8 +25,8 @@ export class RLottiePlayer {
public worker: QueryableWorker; public worker: QueryableWorker;
public width: number; private width = 0;
public height: number; private height = 0;
public listeners: Partial<{ public listeners: Partial<{
[k in RLottiePlayerListeners]: (res: any) => void [k in RLottiePlayerListeners]: (res: any) => void
@ -40,21 +49,38 @@ export class RLottiePlayer {
private frThen: number; private frThen: number;
private rafId: number; private rafId: number;
private playedTimes = 0; //private playedTimes = 0;
constructor({el, width, height, worker}: { constructor({el, worker, options}: {
el: HTMLElement, el: HTMLElement,
width: number, worker: QueryableWorker,
height: number, options: RLottieOptions
worker: QueryableWorker
}) { }) {
this.reqId = ++RLottiePlayer['reqId']; this.reqId = ++RLottiePlayer['reqId'];
this.el = el; this.el = el;
this.width = width;
this.height = height;
this.worker = worker; this.worker = worker;
for(let i in options) {
if(this.hasOwnProperty(i)) {
// @ts-ignore
this[i] = options[i];
}
}
//console.log("RLottiePlayer width:", this.width, this.height, options);
if(window.devicePixelRatio > 1) {
if(isApple) {
this.width *= window.devicePixelRatio;
this.height *= window.devicePixelRatio;
} else {
this.width *= (window.devicePixelRatio - 1.5);
this.height *= (window.devicePixelRatio - 1.5);
}
}
this.canvas = document.createElement('canvas'); this.canvas = document.createElement('canvas');
this.canvas.classList.add('rlottie');
this.canvas.width = this.width; this.canvas.width = this.width;
this.canvas.height = this.height; this.canvas.height = this.height;
this.context = this.canvas.getContext('2d'); this.context = this.canvas.getContext('2d');
@ -134,11 +160,11 @@ export class RLottiePlayer {
public renderFrame(frame: Uint8ClampedArray, frameNo: number) { public renderFrame(frame: Uint8ClampedArray, frameNo: number) {
try { try {
this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0); this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0/* , 0, 0, this.canvas.width, this.canvas.height */);
} catch(err) { } catch(err) {
console.error('RLottiePlayer renderFrame error:', err, frame, this.width, this.height); console.error('RLottiePlayer renderFrame error:', err, frame, this.width, this.height);
this.autoplay = false; this.autoplay = false;
this.stop(); this.pause();
} }
this.setListenerResult('enterFrame', frameNo); this.setListenerResult('enterFrame', frameNo);
@ -170,7 +196,7 @@ export class RLottiePlayer {
private mainLoopForwards() { private mainLoopForwards() {
this.sendQuery('renderFrame', this.curFrame++); this.sendQuery('renderFrame', this.curFrame++);
if(this.curFrame >= this.frameCount) { if(this.curFrame >= this.frameCount) {
this.playedTimes++; //this.playedTimes++;
if(!this.loop) return false; if(!this.loop) return false;
@ -183,7 +209,7 @@ export class RLottiePlayer {
private mainLoopBackwards() { private mainLoopBackwards() {
this.sendQuery('renderFrame', this.curFrame--); this.sendQuery('renderFrame', this.curFrame--);
if(this.curFrame < 0) { if(this.curFrame < 0) {
this.playedTimes++; //this.playedTimes++;
if(!this.loop) return false; if(!this.loop) return false;
@ -385,14 +411,7 @@ class LottieLoader {
} }
} }
public async loadAnimationWorker(params: { public async loadAnimationWorker(params: RLottieOptions, group = '', toneIndex = -1) {
container: HTMLElement,
autoplay?: boolean,
animationData: any,
loop?: boolean,
width?: number,
height?: number
}, group = '', toneIndex = -1) {
params.autoplay = true; params.autoplay = true;
if(toneIndex >= 1 && toneIndex <= 5) { if(toneIndex >= 1 && toneIndex <= 5) {
@ -403,21 +422,16 @@ class LottieLoader {
await this.loadLottieWorkers(); await this.loadLottieWorkers();
} }
const width = params.width || parseInt(params.container.style.width); if(!params.width || !params.height) {
const height = params.height || parseInt(params.container.style.height); params.width = parseInt(params.container.style.width);
params.height = parseInt(params.container.style.height);
}
if(!width || !height) { if(!params.width || !params.height) {
throw new Error('No size for sticker!'); throw new Error('No size for sticker!');
} }
const player = this.initPlayer(params.container, params.animationData, width, height); const player = this.initPlayer(params.container, params);
for(let i in params) {
// @ts-ignore
if(player.hasOwnProperty(i)) {
// @ts-ignore
player[i] = params[i];
}
}
(this.byGroups[group] ?? (this.byGroups[group] = [])).push(player); (this.byGroups[group] ?? (this.byGroups[group] = [])).push(player);
@ -425,7 +439,7 @@ class LottieLoader {
} }
public checkAnimations(blurred?: boolean, group?: string, destroy = false) { public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
const groups = group && false ? [group] : Object.keys(this.byGroups); const groups = group /* && false */ ? [group] : Object.keys(this.byGroups);
if(group && !this.byGroups[group]) { if(group && !this.byGroups[group]) {
console.warn('no animation group:', group); console.warn('no animation group:', group);
@ -438,20 +452,6 @@ class LottieLoader {
animations.forEach(player => { animations.forEach(player => {
this.checkAnimation(player, blurred, destroy); this.checkAnimation(player, blurred, destroy);
//if(!autoplay) continue;
/* if(blurred || !isElementInViewport(container)) {
if(!paused) {
this.debug && console.log('pause animation', isElementInViewport(container), container);
animation.pause();
animations[i].paused = true;
}
} else if(paused) {
this.debug && console.log('play animation', container);
animation.play();
animations[i].paused = false;
} */
}); });
} }
} }
@ -523,12 +523,11 @@ class LottieLoader {
this.workers.length = 0; this.workers.length = 0;
} }
private initPlayer(el: HTMLElement, json: any, width: number, height: number) { private initPlayer(el: HTMLElement, options: RLottieOptions) {
const rlPlayer = new RLottiePlayer({ const rlPlayer = new RLottiePlayer({
el, el,
width, worker: this.workers[this.curWorkerNum++],
height, options
worker: this.workers[this.curWorkerNum++]
}); });
this.players[rlPlayer.reqId] = rlPlayer; this.players[rlPlayer.reqId] = rlPlayer;
@ -536,7 +535,7 @@ class LottieLoader {
this.curWorkerNum = 0; this.curWorkerNum = 0;
} }
rlPlayer.loadFromData(json); rlPlayer.loadFromData(options.animationData);
return rlPlayer; return rlPlayer;
} }

17
src/lib/polyfill.ts

@ -4,25 +4,30 @@ import {SecureRandom} from 'jsbn';
export const secureRandom = new SecureRandom(); export const secureRandom = new SecureRandom();
export function logger(prefix: string) { export enum LogLevels {
log = 1,
warn = 2,
error = 4
};
export function logger(prefix: string, level = LogLevels.log | LogLevels.warn | LogLevels.error) {
function Log(...args: any[]) { function Log(...args: any[]) {
return console.log(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.log && console.log(dT(), '[' + prefix + ']:', ...args);
} }
Log.warn = function(...args: any[]) { Log.warn = function(...args: any[]) {
return console.warn(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.warn && console.warn(dT(), '[' + prefix + ']:', ...args);
}; };
Log.info = function(...args: any[]) { Log.info = function(...args: any[]) {
return console.info(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.log && console.info(dT(), '[' + prefix + ']:', ...args);
}; };
Log.error = function(...args: any[]) { Log.error = function(...args: any[]) {
return console.error(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.error && console.error(dT(), '[' + prefix + ']:', ...args);
}; };
Log.trace = function(...args: any[]) { Log.trace = function(...args: any[]) {
return console.trace(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.log && console.trace(dT(), '[' + prefix + ']:', ...args);
} }
return Log; return Log;

2
src/scss/partials/_chat.scss

@ -437,7 +437,7 @@ $time-background: rgba(0, 0, 0, .35);
position: relative; position: relative;
/* padding: .25rem; */ /* padding: .25rem; */
&.is-reply-media { &.is-media {
.pinned-message-content, .reply-content { .pinned-message-content, .reply-content {
padding-left: 40px; padding-left: 40px;
} }

5
src/scss/style.scss

@ -1354,6 +1354,11 @@ img.emoji {
pointer-events: none; pointer-events: none;
} }
.rlottie {
max-width: 100%;
max-height: 100%;
}
/* #chats-container { /* #chats-container {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;

Loading…
Cancel
Save