Browse Source

New stickers (rlottie)

Media viewer:
1) fix arrows
2) forward
3) item opening
QR-code 2FA login fix
Forward dialogs search (not ready yet)
Webpages square and vertical layouts
master
morethanwords 4 years ago
parent
commit
c4f7dd7b0b
  1. 12
      src/components/appForward.ts
  2. 67
      src/components/appSelectPeers.ts
  3. 6
      src/components/emoticonsDropdown.ts
  4. 6
      src/components/misc.ts
  5. 38
      src/components/wrappers.ts
  6. 13
      src/lib/appManagers/appChatsManager.ts
  7. 4
      src/lib/appManagers/appDialogsManager.ts
  8. 104
      src/lib/appManagers/appImManager.ts
  9. 79
      src/lib/appManagers/appMediaViewer.ts
  10. 128
      src/lib/appManagers/appMessagesManager.ts
  11. 4
      src/lib/appManagers/appPollsManager.ts
  12. 6
      src/lib/appManagers/appSidebarRight.ts
  13. 26
      src/lib/appManagers/appUsersManager.ts
  14. 2
      src/lib/config.ts
  15. 461
      src/lib/lottieLoader copy.ts
  16. 554
      src/lib/lottieLoader.ts
  17. 110
      src/lib/searchIndexManager.ts
  18. 276
      src/lib/utils.js
  19. 76
      src/pages/pageAuthCode.ts
  20. 24
      src/pages/pagePassword.ts
  21. 171
      src/pages/pageSignQR.ts
  22. 8
      src/pages/pagesManager.ts
  23. 38
      src/scss/partials/_chatBubble.scss
  24. 1
      src/scss/partials/_ckin.scss
  25. 48
      src/scss/partials/_mediaViewer.scss
  26. 24
      src/scss/style.scss
  27. 8
      webpack.common.js
  28. 8
      webpack.prod.js

12
src/components/appForward.ts

@ -4,7 +4,7 @@ import { putPreloader } from "./misc"; @@ -4,7 +4,7 @@ import { putPreloader } from "./misc";
import { AppSelectPeers } from "./appSelectPeers";
class AppForward {
private container = document.getElementById('forward-container') as HTMLDivElement;
public container = document.getElementById('forward-container') as HTMLDivElement;
private closeBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
private sendBtn = this.container.querySelector('.btn-circle') as HTMLButtonElement;
@ -12,10 +12,7 @@ class AppForward { @@ -12,10 +12,7 @@ class AppForward {
private msgIDs: number[] = [];
constructor() {
this.closeBtn.addEventListener('click', () => {
this.cleanup();
this.container.classList.remove('active');
});
this.closeBtn.addEventListener('click', this.close.bind(this));
this.sendBtn.addEventListener('click', () => {
let peerIDs = this.selector.getSelected();
@ -45,6 +42,11 @@ class AppForward { @@ -45,6 +42,11 @@ class AppForward {
});
}
public close() {
this.cleanup();
this.container.classList.remove('active');
}
public cleanup() {
if(this.selector) {
this.selector.container.remove();

67
src/components/appSelectPeers.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import Scrollable from "./scrollable_new";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appMessagesManager, { Dialog } from "../lib/appManagers/appMessagesManager";
import { $rootScope, cancelEvent, findUpTag, findUpClassName } from "../lib/utils";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appChatsManager from "../lib/appManagers/appChatsManager";
@ -24,7 +24,7 @@ export class AppSelectPeers { @@ -24,7 +24,7 @@ export class AppSelectPeers {
private myID = $rootScope.myID;
private offsetIndex = 0;
private promise: Promise<number[]>;
private promise: Promise<any>;
private query = '';
private cachedContacts: number[];
@ -47,6 +47,7 @@ export class AppSelectPeers { @@ -47,6 +47,7 @@ export class AppSelectPeers {
this.chatsContainer.classList.add('chats-container');
this.chatsContainer.append(this.list);
this.scrollable = new Scrollable(this.chatsContainer);
this.scrollable.setVirtualContainer(this.list);
this.list.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
@ -89,9 +90,11 @@ export class AppSelectPeers { @@ -89,9 +90,11 @@ export class AppSelectPeers {
if(this.query != value) {
if(this.peerType == 'contacts') {
this.cachedContacts = null;
this.promise = null;
} else {
this.offsetIndex = 0;
}
this.promise = null;
this.list.innerHTML = '';
this.query = value;
@ -115,26 +118,35 @@ export class AppSelectPeers { @@ -115,26 +118,35 @@ export class AppSelectPeers {
}
}
private getMoreDialogs() {
private async getMoreDialogs() {
if(this.promise) return this.promise;
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
return appMessagesManager.getConversations(this.offsetIndex, pageCount, 0).then(value => {
let dialogs = value.dialogs;
let newOffsetIndex = dialogs[value.dialogs.length - 1].index || 0;
dialogs = dialogs.filter(d => d.peerID != this.myID);
if(!this.offsetIndex) {
dialogs.unshift({
peerID: this.myID,
pFlags: {}
} as any);
}
const pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
this.offsetIndex = newOffsetIndex;
this.promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, 0);
const value = await this.promise;
this.renderResults(dialogs.map(dialog => dialog.peerID));
});
let dialogs = value.dialogs as Dialog[];
if(!dialogs.length) {
return;
}
const newOffsetIndex = dialogs[dialogs.length - 1].index || 0;
dialogs = dialogs.filter(d => d.peerID != this.myID);
if(!this.offsetIndex) {
dialogs.unshift({
peerID: this.myID,
pFlags: {}
} as any);
}
this.offsetIndex = newOffsetIndex;
this.renderResults(dialogs.map(dialog => dialog.peerID));
this.promise = null;
}
private async getMoreContacts() {
@ -148,8 +160,8 @@ export class AppSelectPeers { @@ -148,8 +160,8 @@ export class AppSelectPeers {
}
if(this.cachedContacts.length) {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
let arr = this.cachedContacts.splice(0, pageCount);
const pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
const arr = this.cachedContacts.splice(0, pageCount);
this.renderResults(arr);
}
}
@ -163,8 +175,9 @@ export class AppSelectPeers { @@ -163,8 +175,9 @@ export class AppSelectPeers {
}
private renderResults(peerIDs: number[]) {
console.log('will renderResults:', peerIDs);
peerIDs.forEach(peerID => {
let {dom} = appDialogsManager.addDialog(peerID, this.list, false, false);
const {dom} = appDialogsManager.addDialog(peerID, this.scrollable, false, false);
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
let subtitle = '';
@ -184,14 +197,14 @@ export class AppSelectPeers { @@ -184,14 +197,14 @@ export class AppSelectPeers {
}
private add(peerID: number) {
let div = document.createElement('div');
const div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
div.dataset.peerID = '' + peerID;
this.selected[peerID] = div;
let title = appPeersManager.getPeerTitle(peerID, false, true);
const title = appPeersManager.getPeerTitle(peerID, false, true);
let avatarEl = document.createElement('avatar-element');
const avatarEl = document.createElement('avatar-element');
avatarEl.classList.add('selector-user-avatar', 'tgico');
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + peerID);
@ -205,7 +218,7 @@ export class AppSelectPeers { @@ -205,7 +218,7 @@ export class AppSelectPeers {
}
private remove(peerID: number) {
let div = this.selected[peerID];
const div = this.selected[peerID];
div.classList.remove('scale-in');
void div.offsetWidth;
div.classList.add('scale-out');

6
src/components/emoticonsDropdown.ts

@ -254,7 +254,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -254,7 +254,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
stickersDiv.classList.add('stickers-categories');
contentStickersDiv.append(stickersDiv);
stickersDiv.addEventListener('mouseover', (e) => {
/* stickersDiv.addEventListener('mouseover', (e) => {
let target = e.target as HTMLElement;
if(target.tagName == 'CANVAS') { // turn on sticker
@ -269,7 +269,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -269,7 +269,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
}
}
}
});
}); */
stickersDiv.addEventListener('click', onMediaClick);
@ -397,7 +397,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -397,7 +397,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
const text = e.srcElement.result;
let json = await apiManager.gzipUncompress<string>(text, true);
let animation = await lottieLoader.loadAnimation({
let animation = await lottieLoader.loadAnimationWorker({
container: li,
loop: true,
autoplay: false,

6
src/components/misc.ts

@ -341,10 +341,10 @@ export function formatPhoneNumber(str: string) { @@ -341,10 +341,10 @@ export function formatPhoneNumber(str: string) {
return {formatted: str, country};
}
export function parseMenuButtonsTo(to: {[name: string]: HTMLButtonElement}, elements: HTMLCollection) {
export function parseMenuButtonsTo(to: {[name: string]: HTMLElement}, elements: HTMLCollection | NodeListOf<HTMLElement>) {
Array.from(elements).forEach(el => {
let name = el.className.match(/ menu-(.+?) /)[1];
to[name] = el as HTMLButtonElement;
let name = el.className.match(/(?:^|\s)menu-(.+?)(?:$|\s)/)[1];
to[name] = el as HTMLElement;
});
}

38
src/components/wrappers.ts

@ -697,7 +697,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -697,7 +697,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
let stickerType = doc.sticker;
if(stickerType == 2 && !LottieLoader.loaded) {
LottieLoader.loadLottie();
//LottieLoader.loadLottie();
LottieLoader.loadLottieWorkers();
}
if(!stickerType) {
@ -759,7 +760,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -759,7 +760,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
div.append(img);
}
lazyLoadQueue && !downloaded ? lazyLoadQueue.push({div, load}) : load();
lazyLoadQueue && !downloaded ? lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}) : load();
}
}
@ -779,7 +780,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -779,7 +780,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
let downloaded = doc.downloaded;
let load = () => appDocsManager.downloadDoc(doc.id).then(blob => {
//console.log('loaded sticker:', blob, div);
//console.log('loaded sticker:', doc, div);
if(middleware && !middleware()) return;
//return;
@ -796,21 +797,26 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -796,21 +797,26 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
//console.timeEnd('decompress sticker' + doc.id);
let animation = await LottieLoader.loadAnimation({
/* if(doc.id == '1860749763008266301') {
console.log('loaded sticker:', doc, div);
} */
let animation = await LottieLoader.loadAnimationWorker/* loadAnimation */({
container: div,
loop: false,
autoplay: false,
animationData: JSON.parse(json),
renderer: 'svg'
loop: !emoji,
autoplay: true,
animationData: JSON.parse(json)
}, group, toneIndex);
animation.addListener('ready', () => {
if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') {
div.firstElementChild.remove();
}
});
//console.timeEnd('render sticker' + doc.id);
if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') {
div.firstElementChild.remove();
}
div.addEventListener('mouseover', (e) => {
/* div.addEventListener('mouseover', (e) => {
let animation = LottieLoader.getAnimation(div, group);
if(animation) {
@ -833,9 +839,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -833,9 +839,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}
});
if(play) {
if(play && false) {
animation.play();
}
} */
});
reader.readAsArrayBuffer(blob);
@ -862,7 +868,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -862,7 +868,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}
});
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), 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) {

13
src/lib/appManagers/appChatsManager.ts

@ -1,9 +1,10 @@ @@ -1,9 +1,10 @@
import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils";
import { $rootScope, isObject, safeReplaceObject, copy, numberWithCommas } from "../utils";
import { RichTextProcessor } from "../richtextprocessor";
import appUsersManager from "./appUsersManager";
import apiManager from '../mtproto/mtprotoworker';
import apiUpdatesManager from "./apiUpdatesManager";
import appProfileManager from "./appProfileManager";
import searchIndexManager from "../searchIndexManager";
type Channel = {
_: 'channel',
@ -72,7 +73,7 @@ export class AppChatsManager { @@ -72,7 +73,7 @@ export class AppChatsManager {
let oldChat = this.chats[apiChat.id];
let titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '', false).split(' ');
let titleWords = searchIndexManager.cleanSearchText(apiChat.title || '', false).split(' ');
let firstWord = titleWords.shift();
let lastWord = titleWords.pop();
apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : '');
@ -95,7 +96,7 @@ export class AppChatsManager { @@ -95,7 +96,7 @@ export class AppChatsManager {
}
if(apiChat.username) {
let searchUsername = SearchIndexManager.cleanUsername(apiChat.username);
let searchUsername = searchIndexManager.cleanUsername(apiChat.username);
this.usernames[searchUsername] = apiChat.id;
}
@ -297,10 +298,10 @@ export class AppChatsManager { @@ -297,10 +298,10 @@ export class AppChatsManager {
}
public getChatMembersString(id: number) {
let chat = this.getChat(id);
const chat = this.getChat(id);
let isChannel = this.isChannel(id) && !this.isMegagroup(id);
let participants_count = chat.participants_count || chat.participants.participants.length;
const isChannel = this.isBroadcast(id);
const participants_count = chat.participants_count || chat.participants?.participants.length || 0;
return numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
}

4
src/lib/appManagers/appDialogsManager.ts

@ -489,7 +489,7 @@ export class AppDialogsManager { @@ -489,7 +489,7 @@ export class AppDialogsManager {
console.time('getDialogs time');
let loadCount = 50/*this.chatsLoadCount */;
this.loadDialogsPromise = appMessagesManager.getConversations(offsetIndex, loadCount, +archived);
this.loadDialogsPromise = appMessagesManager.getConversations('', offsetIndex, loadCount, +archived);
let result = await this.loadDialogsPromise;
@ -792,7 +792,7 @@ export class AppDialogsManager { @@ -792,7 +792,7 @@ export class AppDialogsManager {
return this.doms[peerID] || this.domsArchived[peerID];
}
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement, drawStatus = true, rippleEnabled = true, onlyFirstName = false) {
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable, drawStatus = true, rippleEnabled = true, onlyFirstName = false) {
let dialog: Dialog;
if(typeof(_dialog) === 'number') {

104
src/lib/appManagers/appImManager.ts

@ -260,13 +260,6 @@ export class AppImManager { @@ -260,13 +260,6 @@ export class AppImManager {
private scrolledAllDown: boolean;
public contextMenu = new ChatContextMenu(this.bubblesContainer);
private popupDeleteMessage: {
popupEl?: HTMLDivElement,
deleteBothBtn?: HTMLButtonElement,
deleteMeBtn?: HTMLButtonElement,
cancelBtn?: HTMLButtonElement
} = {};
private setPeerPromise: Promise<boolean> = null;
@ -288,9 +281,12 @@ export class AppImManager { @@ -288,9 +281,12 @@ export class AppImManager {
private peerChanged: boolean;
private firstUnreadBubble: HTMLDivElement = null;
private attachedUnreadBubble: boolean;
private stickyIntersector: StickyIntersector = null;
private cleanupID = 0;
constructor() {
/* if(!lottieLoader.loaded) {
lottieLoader.loadLottie();
@ -301,12 +297,7 @@ export class AppImManager { @@ -301,12 +297,7 @@ export class AppImManager {
this.chatInputC = new ChatInput();
this.preloader = new ProgressivePreloader(null, false);
this.popupDeleteMessage.popupEl = this.pageEl.querySelector('.popup-delete-message') as HTMLDivElement;
this.popupDeleteMessage.deleteBothBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-both') as HTMLButtonElement;
this.popupDeleteMessage.deleteMeBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-me') as HTMLButtonElement;
this.popupDeleteMessage.cancelBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-close') as HTMLButtonElement;
apiManager.getUserID().then((id) => {
this.myID = $rootScope.myID = id;
});
@ -657,8 +648,16 @@ export class AppImManager { @@ -657,8 +648,16 @@ export class AppImManager {
return;
}
if(e.key == 'Escape' && this.peerID != 0) { // hide current dialog
this.setPeer(0);
if(e.key == 'Escape') {
if(appMediaViewer.wholeDiv.classList.contains('active')) {
appMediaViewer.buttons.close.click();
} else if(appForward.container.classList.contains('active')) {
appForward.close();
} else if(this.chatInputC.replyElements.container.classList.contains('active')) {
this.chatInputC.replyElements.cancelBtn.click();
} else if(this.peerID != 0) { // hide current dialog
this.setPeer(0);
}
} else if(e.key == 'Meta' || e.key == 'Control') {
return;
} else if(e.key == 'c' && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') {
@ -672,16 +671,6 @@ export class AppImManager { @@ -672,16 +671,6 @@ export class AppImManager {
};
document.body.addEventListener('keydown', onKeyDown);
this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
appMessagesManager.deleteMessages([this.contextMenu.msgID], true);
this.popupDeleteMessage.cancelBtn.click();
});
this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => {
appMessagesManager.deleteMessages([this.contextMenu.msgID], false);
this.popupDeleteMessage.cancelBtn.click();
});
this.goDownBtn.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
@ -939,6 +928,7 @@ export class AppImManager { @@ -939,6 +928,7 @@ export class AppImManager {
this.peerChanged = false;
this.firstUnreadBubble = null;
this.attachedUnreadBubble = false;
this.messagesQueue.length = 0;
this.messagesQueuePromise = null;
@ -954,6 +944,8 @@ export class AppImManager { @@ -954,6 +944,8 @@ export class AppImManager {
this.loadedTopTimes = this.loadedBottomTimes = 0;
this.cleanupID++;
////console.timeEnd('appImManager cleanup');
}
@ -1094,7 +1086,8 @@ export class AppImManager { @@ -1094,7 +1086,8 @@ export class AppImManager {
this.log('scrolledAllDown:', this.scrolledAllDown);
if(!this.unreaded.length && dialog) { // lol
//if(!this.unreaded.length && dialog) { // lol
if(this.scrolledAllDown && dialog) { // lol
appMessagesManager.readHistory(peerID, dialog.top_message);
}
@ -1401,6 +1394,13 @@ export class AppImManager { @@ -1401,6 +1394,13 @@ export class AppImManager {
});
}
}
private getMiddleware() {
let cleanupID = this.cleanupID;
return () => {
return this.cleanupID == cleanupID;
};
}
// reverse means top
public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) {
@ -1766,16 +1766,12 @@ export class AppImManager { @@ -1766,16 +1766,12 @@ export class AppImManager {
wrapAlbum({
groupID: message.grouped_id,
attachmentDiv,
middleware: () => {
return this.peerID == peerID;
},
middleware: this.getMiddleware(),
isOut: our,
lazyLoadQueue: this.lazyLoadQueue
});
} else {
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, isOut, this.lazyLoadQueue, () => {
return this.peerID == peerID;
});
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, true, isOut, this.lazyLoadQueue, this.getMiddleware());
}
break;
@ -1827,9 +1823,7 @@ export class AppImManager { @@ -1827,9 +1823,7 @@ export class AppImManager {
boxWidth: 380,
boxHeight: 300,
lazyLoadQueue: this.lazyLoadQueue,
middleware: () => {
return this.peerID == peerID;
},
middleware: this.getMiddleware(),
isOut
});
//}
@ -1841,9 +1835,14 @@ export class AppImManager { @@ -1841,9 +1835,14 @@ export class AppImManager {
if(webpage.photo && !doc) {
bubble.classList.add('photo');
wrapPhoto(webpage.photo.id, message, preview, 380, 300, false, null, this.lazyLoadQueue, () => {
return this.peerID == peerID;
});
const size = webpage.photo.sizes[webpage.photo.sizes.length - 1];
if(size.w == size.h) {
bubble.classList.add('is-square-photo');
} else if(size.h > size.w) {
bubble.classList.add('is-vertical-photo');
}
wrapPhoto(webpage.photo.id, message, preview, 380, 300, false, null, this.lazyLoadQueue, this.getMiddleware());
}
if(preview) {
@ -1861,8 +1860,13 @@ export class AppImManager { @@ -1861,8 +1860,13 @@ export class AppImManager {
if(webpage.title) {
titleDiv.innerHTML = RichTextProcessor.wrapRichText(webpage.title);
}
let quoteTextDiv = document.createElement('div');
quoteTextDiv.classList.add('quote-text');
quoteTextDiv.append(nameEl, titleDiv, textDiv);
quote.append(quoteTextDiv);
quote.append(nameEl, titleDiv, textDiv);
box.append(quote);
//bubble.prepend(box);
@ -1894,14 +1898,7 @@ export class AppImManager { @@ -1894,14 +1898,7 @@ export class AppImManager {
wrapSticker({
doc,
div: attachmentDiv,
middleware: () => {
if(this.peerID != peerID) {
this.log.warn('peer changed, canceling sticker attach');
return false;
}
return true;
},
middleware: this.getMiddleware(),
lazyLoadQueue: this.lazyLoadQueue,
group: 'chat',
play: !!message.pending || !multipleRender,
@ -1923,9 +1920,7 @@ export class AppImManager { @@ -1923,9 +1920,7 @@ export class AppImManager {
wrapAlbum({
groupID: message.grouped_id,
attachmentDiv,
middleware: () => {
return this.peerID == peerID;
},
middleware: this.getMiddleware(),
isOut: our,
lazyLoadQueue: this.lazyLoadQueue
});
@ -1939,9 +1934,7 @@ export class AppImManager { @@ -1939,9 +1934,7 @@ export class AppImManager {
withTail: doc.type != 'round',
isOut: isOut,
lazyLoadQueue: this.lazyLoadQueue,
middleware: () => {
return this.peerID == peerID;
}
middleware: this.getMiddleware()
});
}
@ -2325,6 +2318,10 @@ export class AppImManager { @@ -2325,6 +2318,10 @@ export class AppImManager {
}
public setUnreadDelimiter() {
if(this.attachedUnreadBubble) {
return;
}
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(!dialog?.unread_count) return;
@ -2343,6 +2340,7 @@ export class AppImManager { @@ -2343,6 +2340,7 @@ export class AppImManager {
}
this.firstUnreadBubble = bubble;
this.attachedUnreadBubble = true;
}
}

79
src/lib/appManagers/appMediaViewer.ts

@ -4,35 +4,27 @@ import appMessagesManager from "./appMessagesManager"; @@ -4,35 +4,27 @@ import appMessagesManager from "./appMessagesManager";
import { RichTextProcessor } from "../richtextprocessor";
import { logger } from "../polyfill";
import ProgressivePreloader from "../../components/preloader";
import { findUpClassName, $rootScope, generatePathData, fillPropertyValue } from "../utils";
import { findUpClassName, $rootScope, generatePathData, fillPropertyValue, cancelEvent } from "../utils";
import appDocsManager from "./appDocsManager";
import VideoPlayer from "../mediaPlayer";
import { renderImageFromUrl } from "../../components/misc";
import { renderImageFromUrl, parseMenuButtonsTo } from "../../components/misc";
import AvatarElement from "../../components/avatar";
import LazyLoadQueue from "../../components/lazyLoadQueue";
import appForward from "../../components/appForward";
export class AppMediaViewer {
private overlaysDiv = document.querySelector('.overlays') as HTMLDivElement;
private mediaViewerDiv = this.overlaysDiv.firstElementChild as HTMLDivElement;
public wholeDiv = document.querySelector('.media-viewer-whole') as HTMLDivElement;
private overlaysDiv = this.wholeDiv.firstElementChild as HTMLDivElement;
private author = {
avatarEl: this.overlaysDiv.querySelector('.media-viewer-userpic') as AvatarElement,
nameEl: this.overlaysDiv.querySelector('.media-viewer-name') as HTMLDivElement,
date: this.overlaysDiv.querySelector('.media-viewer-date') as HTMLDivElement
};
private buttons = {
delete: this.overlaysDiv.querySelector('.media-viewer-delete-button') as HTMLDivElement,
forward: this.overlaysDiv.querySelector('.media-viewer-forward-button') as HTMLDivElement,
download: this.overlaysDiv.querySelector('.media-viewer-download-button') as HTMLDivElement,
close: this.overlaysDiv.querySelector('.media-viewer-close-button') as HTMLDivElement,
prev: this.overlaysDiv.querySelector('.media-viewer-switcher-left') as HTMLDivElement,
next: this.overlaysDiv.querySelector('.media-viewer-switcher-right') as HTMLDivElement,
};
private content = {
container: this.overlaysDiv.querySelector('.media-viewer-media') as HTMLDivElement,
caption: this.overlaysDiv.querySelector('.media-viewer-caption') as HTMLDivElement,
//mover: this.overlaysDiv.querySelector('.media-viewer-mover') as HTMLDivElement
mover: document.querySelector('.media-viewer-mover') as HTMLDivElement
public buttons: {[k in 'delete' | 'forward' | 'download' | 'close' | 'prev' | 'next']: HTMLElement} = {} as any;
private content: {[k in 'container' | 'caption' | 'mover']: HTMLDivElement} = {
container: this.overlaysDiv.querySelector('.media-viewer-media'),
caption: this.overlaysDiv.querySelector('.media-viewer-caption'),
mover: null
};
public currentMessageID = 0;
@ -71,9 +63,12 @@ export class AppMediaViewer { @@ -71,9 +63,12 @@ export class AppMediaViewer {
this.preloader = new ProgressivePreloader();
this.lazyLoadQueue = new LazyLoadQueue(5, false);
parseMenuButtonsTo(this.buttons, this.wholeDiv.querySelectorAll(`[class*='menu']`) as NodeListOf<HTMLElement>);
this.onKeyDownBinded = this.onKeyDown.bind(this);
this.buttons.close.addEventListener('click', () => {
this.buttons.close.addEventListener('click', (e) => {
cancelEvent(e);
//this.overlaysDiv.classList.remove('active');
this.content.container.innerHTML = '';
if(this.content.container.firstElementChild) {
@ -92,10 +87,13 @@ export class AppMediaViewer { @@ -92,10 +87,13 @@ export class AppMediaViewer {
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
appForward.close();
window.removeEventListener('keydown', this.onKeyDownBinded);
});
this.buttons.prev.addEventListener('click', () => {
this.buttons.prev.addEventListener('click', (e) => {
cancelEvent(e);
if(this.setMoverPromise) return;
let target = this.prevTargets.pop();
@ -107,7 +105,8 @@ export class AppMediaViewer { @@ -107,7 +105,8 @@ export class AppMediaViewer {
}
});
this.buttons.next.addEventListener('click', () => {
this.buttons.next.addEventListener('click', (e) => {
cancelEvent(e);
if(this.setMoverPromise) return;
let target = this.nextTargets.shift();
@ -141,6 +140,7 @@ export class AppMediaViewer { @@ -141,6 +140,7 @@ export class AppMediaViewer {
});
this.onClickBinded = (e: MouseEvent) => {
cancelEvent(e);
let target = e.target as HTMLElement;
let mover: HTMLDivElement = null;
@ -156,9 +156,10 @@ export class AppMediaViewer { @@ -156,9 +156,10 @@ export class AppMediaViewer {
}
};
this.overlaysDiv.addEventListener('click', this.onClickBinded);
this.content.mover.addEventListener('click', this.onClickBinded);
this.wholeDiv.addEventListener('click', this.onClickBinded);
//this.content.mover.addEventListener('click', this.onClickBinded);
//this.content.mover.append(this.buttons.prev, this.buttons.next);
this.setNewMover();
}
public onKeyDown(e: KeyboardEvent) {
@ -176,7 +177,7 @@ export class AppMediaViewer { @@ -176,7 +177,7 @@ export class AppMediaViewer {
if(!closing) {
mover.innerHTML = '';
mover.append(this.buttons.prev, this.buttons.next);
//mover.append(this.buttons.prev, this.buttons.next);
}
let wasActive = fromRight !== 0;
@ -357,7 +358,7 @@ export class AppMediaViewer { @@ -357,7 +358,7 @@ export class AppMediaViewer {
mediaElement.src = src;
}
});
} else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && (mediaElement.firstElementChild as HTMLSourceElement).src) {
} else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && ((mediaElement.firstElementChild as HTMLSourceElement).src || src)) {
await new Promise((resolve, reject) => {
mediaElement.addEventListener('loadeddata', resolve);
@ -386,7 +387,7 @@ export class AppMediaViewer { @@ -386,7 +387,7 @@ export class AppMediaViewer {
}
setTimeout(() => {
this.overlaysDiv.classList.remove('active');
this.wholeDiv.classList.remove('active');
}, 0);
setTimeout(() => {
@ -409,6 +410,8 @@ export class AppMediaViewer { @@ -409,6 +410,8 @@ export class AppMediaViewer {
//await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => window.requestAnimationFrame(resolve));
//throw '';
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
if(aspecter) {
@ -427,10 +430,12 @@ export class AppMediaViewer { @@ -427,10 +430,12 @@ export class AppMediaViewer {
mover.classList.remove('moving');
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
mover.classList.remove('active');
//aspecter.style.cssText = '';
void mover.offsetLeft; // reflow
if(mover.querySelector('video')) {
mover.classList.remove('active');
aspecter.style.cssText = '';
void mover.offsetLeft; // reflow
}
aspecter.classList.remove('disable-hover');
}
@ -453,9 +458,9 @@ export class AppMediaViewer { @@ -453,9 +458,9 @@ export class AppMediaViewer {
}
let {width, height} = rect;
if(proportion == 1) {
/* if(proportion == 1) {
aspecter.style.cssText = '';
} else {
} else { */
if(proportion > 0) {
width = height * proportion;
} else {
@ -465,7 +470,7 @@ export class AppMediaViewer { @@ -465,7 +470,7 @@ export class AppMediaViewer {
//this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`);
aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`;
}
//}
}
public sizeTailPath(path: SVGPathElement, rect: DOMRect, scaleX: number, delay: number, upscale: boolean, isOut: boolean, borderRadius: string) {
@ -528,8 +533,12 @@ export class AppMediaViewer { @@ -528,8 +533,12 @@ export class AppMediaViewer {
let newMover = document.createElement('div');
newMover.classList.add('media-viewer-mover');
let oldMover = this.content.mover;
oldMover.parentElement.append(newMover);
if(this.content.mover) {
let oldMover = this.content.mover;
oldMover.parentElement.append(newMover);
} else {
this.wholeDiv.append(newMover);
}
newMover.addEventListener('click', this.onClickBinded);
@ -716,7 +725,7 @@ export class AppMediaViewer { @@ -716,7 +725,7 @@ export class AppMediaViewer {
this.setNewMover();
} else {
window.addEventListener('keydown', this.onKeyDownBinded);
this.overlaysDiv.classList.add('active');
this.wholeDiv.classList.add('active');
}
////////this.log('wasActive:', wasActive);

128
src/lib/appManagers/appMessagesManager.ts

@ -22,6 +22,7 @@ import apiManager from '../mtproto/mtprotoworker'; @@ -22,6 +22,7 @@ import apiManager from '../mtproto/mtprotoworker';
import appWebPagesManager from "./appWebPagesManager";
import { CancellablePromise, deferredPromise } from "../polyfill";
import appPollsManager from "./appPollsManager";
import searchIndexManager from '../searchIndexManager';
const APITIMEOUT = 0;
@ -107,6 +108,17 @@ export class AppMessagesManager { @@ -107,6 +108,17 @@ export class AppMessagesManager {
public loaded: Promise<any> = null;
private dialogsIndex = searchIndexManager.createIndex();
private cachedResults: {
query: string,
count: number,
dialogs: Dialog[]
} = {
query: '',
count: 0,
dialogs: []
};
constructor() {
$rootScope.$on('apiUpdate', (e: CustomEvent) => {
let update: any = e.detail;
@ -1226,9 +1238,33 @@ export class AppMessagesManager { @@ -1226,9 +1238,33 @@ export class AppMessagesManager {
return false;
}
public getConversations(offsetIndex?: number, limit = 20, folderID = 0) {
public getConversations(query = '', offsetIndex?: number, limit = 20, folderID = 0) {
let curDialogStorage = this.dialogsStorage[folderID] ?? (this.dialogsStorage[folderID] = []);
if(query) {
if(!limit || this.cachedResults.query !== query) {
this.cachedResults.query = query
const results = searchIndexManager.search(query, this.dialogsIndex);
this.cachedResults.dialogs = [];
for(const folderID in this.dialogsStorage) {
const dialogs = this.dialogsStorage[folderID];
dialogs.forEach(dialog => {
if(results[dialog.peerID]) {
this.cachedResults.dialogs.push(dialog);
}
});
}
this.cachedResults.count = this.cachedResults.dialogs.length;
}
curDialogStorage = this.cachedResults.dialogs;
} else {
this.cachedResults.query = '';
}
let offset = 0;
if(offsetIndex > 0) {
for(; offset < curDialogStorage.length; offset++) {
@ -1238,15 +1274,15 @@ export class AppMessagesManager { @@ -1238,15 +1274,15 @@ export class AppMessagesManager {
}
}
if(this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) {
if(query || this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) {
return Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit),
count: curDialogStorage.length
});
}
return this.getTopMessages(limit, folderID).then(count => {
let curDialogStorage = this.dialogsStorage[folderID];
return this.getTopMessages(limit, folderID).then(totalCount => {
//const curDialogStorage = this.dialogsStorage[folderID];
offset = 0;
if(offsetIndex > 0) {
@ -1261,7 +1297,7 @@ export class AppMessagesManager { @@ -1261,7 +1297,7 @@ export class AppMessagesManager {
return {
dialogs: curDialogStorage.slice(offset, offset + limit),
count: count
count: totalCount
};
});
}
@ -1277,15 +1313,13 @@ export class AppMessagesManager { @@ -1277,15 +1313,13 @@ export class AppMessagesManager {
if(this.dialogsOffsetDate[folderID]) {
offsetDate = this.dialogsOffsetDate[folderID] + serverTimeManager.serverTimeOffset;
offsetIndex = this.dialogsOffsetDate[folderID] * 0x10000;
flags |= 1;
//flags |= 1; // means pinned already loaded
}
if(folderID > 0) {
flags |= 1;
//if(folderID > 0) {
//flags |= 1;
flags |= 2;
}
let hash = 0;
//}
return apiManager.invokeApi('messages.getDialogs', {
flags: flags,
@ -1294,7 +1328,7 @@ export class AppMessagesManager { @@ -1294,7 +1328,7 @@ export class AppMessagesManager {
offset_id: appMessagesIDsManager.getMessageLocalID(offsetID),
offset_peer: appPeersManager.getInputPeerByID(offsetPeerID),
limit: limit,
hash: hash
hash: 0
}, {
timeout: APITIMEOUT
}).then((dialogsResult: any) => {
@ -1828,9 +1862,11 @@ export class AppMessagesManager { @@ -1828,9 +1862,11 @@ export class AppMessagesManager {
let messageText = '';
if(message.media) {
switch(message.media._) {
if(message.grouped_id) {
messageText += '<i>Album' + (message.message ? ', ' : '') + '</i>';
} else switch(message.media._) {
case 'messageMediaPhoto':
messageText += '<i>' + (message.grouped_id ? 'Album' : 'Photo') + (message.message ? ', ' : '') + '</i>';
messageText += '<i>Photo' + (message.message ? ', ' : '') + '</i>';
break;
case 'messageMediaGeo':
messageText += '<i>Geolocation</i>';
@ -1869,26 +1905,33 @@ export class AppMessagesManager { @@ -1869,26 +1905,33 @@ export class AppMessagesManager {
if(message.action) {
let action = message.action;
console.log('message action:', action);
let suffix = '';
let _ = action._;
if(_ == "messageActionPhoneCall") {
_ += '.' + action.type;
let duration = action.duration;
if(duration) {
let d = [];
d.push(duration % 60 + ' s');
if(duration >= 60) d.push((duration / 60 | 0) + ' min');
//if(duration >= 3600) d.push((duration / 3600 | 0) + ' h');
suffix = ' (' + d.reverse().join(' ') + ')';
let str = '';
if(action.message) {
str = RichTextProcessor.wrapRichText(action.message, {noLinebreaks: true});
} else {
let suffix = '';
let _ = action._;
if(_ == "messageActionPhoneCall") {
_ += '.' + action.type;
let duration = action.duration;
if(duration) {
let d = [];
d.push(duration % 60 + ' s');
if(duration >= 60) d.push((duration / 60 | 0) + ' min');
//if(duration >= 3600) d.push((duration / 3600 | 0) + ' h');
suffix = ' (' + d.reverse().join(' ') + ')';
}
}
// @ts-ignore
str = langPack[_] + suffix;
}
// @ts-ignore
messageText = '<i>' + langPack[_] + suffix + '</i>';
console.log('message action:', action);
messageText = '<i>' + str + '</i>';
}
let messageWrapped = '';
@ -2098,18 +2141,21 @@ export class AppMessagesManager { @@ -2098,18 +2141,21 @@ export class AppMessagesManager {
}
public saveConversation(dialog: Dialog) {
var peerID = appPeersManager.getPeerID(dialog.peer);
const peerID = appPeersManager.getPeerID(dialog.peer);
if(!peerID) {
return false;
}
var channelID = appPeersManager.isChannel(peerID) ? -peerID : 0;
const channelID = appPeersManager.isChannel(peerID) ? -peerID : 0;
const peerText = appPeersManager.getPeerSearchText(peerID);
searchIndexManager.indexObject(peerID, peerText, this.dialogsIndex);
let mid: number, message;
if(dialog.top_message) {
var mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID);
var message = this.getMessage(mid);
mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID);
message = this.getMessage(mid);
} else {
var mid = this.tempID--;
var message: any = {
mid = this.tempID--;
message = {
_: 'message',
id: mid,
mid: mid,
@ -2120,14 +2166,14 @@ export class AppMessagesManager { @@ -2120,14 +2166,14 @@ export class AppMessagesManager {
pFlags: {unread: false, out: true},
date: 0,
message: ''
}
};
this.saveMessages([message]);
}
if(!channelID && peerID < 0) {
var chat = appChatsManager.getChat(-peerID)
const chat = appChatsManager.getChat(-peerID);
if(chat && chat.migrated_to && chat.pFlags.deactivated) {
var migratedToPeer = appPeersManager.getPeerID(chat.migrated_to)
const migratedToPeer = appPeersManager.getPeerID(chat.migrated_to);
this.migratedFromTo[peerID] = migratedToPeer;
this.migratedToFrom[migratedToPeer] = peerID;
return;
@ -2151,7 +2197,7 @@ export class AppMessagesManager { @@ -2151,7 +2197,7 @@ export class AppMessagesManager {
}
if(this.historiesStorage[peerID] === undefined/* && !message.deleted */) { // warning
let historyStorage: HistoryStorage = {count: null, history: [], pending: []};
const historyStorage: HistoryStorage = {count: null, history: [], pending: []};
historyStorage[mid > 0 ? 'history' : 'pending'].push(mid);
if(mid < 0 && message.pFlags.unread) {
dialog.unread_count++;

4
src/lib/appManagers/appPollsManager.ts

@ -148,4 +148,6 @@ class AppPollsManager { @@ -148,4 +148,6 @@ class AppPollsManager {
}
}
export default new AppPollsManager();
const appPollsManager = new AppPollsManager();
(window as any).appPollsManager = appPollsManager;
export default appPollsManager;

6
src/lib/appManagers/appSidebarRight.ts

@ -15,6 +15,7 @@ import LazyLoadQueue from "../../components/lazyLoadQueue"; @@ -15,6 +15,7 @@ import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument, wrapAudio } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
import AvatarElement from "../../components/avatar";
import appForward from "../../components/appForward";
const testScroll = false;
@ -250,6 +251,11 @@ class AppSidebarRight { @@ -250,6 +251,11 @@ class AppSidebarRight {
(item.element.firstElementChild as HTMLElement).style.display = '';
}
if(enable == false || (this.sidebarEl.classList.contains('active') && enable == undefined)) {
appForward.close();
this.searchCloseBtn.click();
}
resolve();
}, 200);
});

26
src/lib/appManagers/appUsersManager.ts

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import { SearchIndexManager, safeReplaceObject, isObject, tsNow, copy, $rootScope } from "../utils";
import { safeReplaceObject, isObject, tsNow, copy, $rootScope } from "../utils";
import { RichTextProcessor } from "../richtextprocessor";
import appChatsManager from "./appChatsManager";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import serverTimeManager from "../mtproto/serverTimeManager";
import { formatPhoneNumber } from "../../components/misc";
import searchIndexManager from "../searchIndexManager";
export type User = {
_: 'user',
@ -39,7 +40,7 @@ export class AppUsersManager { @@ -39,7 +40,7 @@ export class AppUsersManager {
public usernames: {[username: string]: number} = {};
public userAccess: {[userID: number]: string} = {};
public cachedPhotoLocations: any = {};
public contactsIndex = SearchIndexManager.createIndex();
public contactsIndex = searchIndexManager.createIndex();
public contactsFillPromise: Promise<number[]>;
public contactsList: number[];
public myID: number;
@ -118,14 +119,14 @@ export class AppUsersManager { @@ -118,14 +119,14 @@ export class AppUsersManager {
return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', {
hash: 0
}).then((result: any) => {
var userID;
let userID: number;
this.contactsList = [];
this.saveApiUsers(result.users);
result.contacts.forEach((contact: any) => {
userID = contact.user_id;
this.contactsList.push(userID);
SearchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
searchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
});
return this.contactsList;
@ -133,15 +134,12 @@ export class AppUsersManager { @@ -133,15 +134,12 @@ export class AppUsersManager {
}
public getUserSearchText(id: number) {
var user = this.users[id];
const user = this.users[id];
if(!user) {
return false;
}
var serviceText = '';
if(user.pFlags.self) {
serviceText = 'user_name_saved_msgs_raw';
return '';
}
const serviceText = user.pFlags.self ? 'user_name_saved_msgs_raw' : '';
return (user.first_name || '') +
' ' + (user.last_name || '') +
' ' + (user.phone || '') +
@ -152,7 +150,7 @@ export class AppUsersManager { @@ -152,7 +150,7 @@ export class AppUsersManager {
public getContacts(query?: string) {
return this.fillContacts().then(contactsList => {
if(query) {
const results: any = SearchIndexManager.search(query, this.contactsIndex);
const results: any = searchIndexManager.search(query, this.contactsIndex);
const filteredContactsList = contactsList.filter(id => !!results[id]);
contactsList = filteredContactsList;
@ -219,11 +217,11 @@ export class AppUsersManager { @@ -219,11 +217,11 @@ export class AppUsersManager {
}
if(apiUser.username) {
var searchUsername = SearchIndexManager.cleanUsername(apiUser.username);
var searchUsername = searchIndexManager.cleanUsername(apiUser.username);
this.usernames[searchUsername] = userID;
}
apiUser.sortName = apiUser.pFlags.deleted ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''), false);
apiUser.sortName = apiUser.pFlags.deleted ? '' : searchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''), false);
var nameWords = apiUser.sortName.split(' ');
var firstWord = nameWords.shift();
@ -544,7 +542,7 @@ export class AppUsersManager { @@ -544,7 +542,7 @@ export class AppUsersManager {
if(isContact != curIsContact) {
if(isContact) {
this.contactsList.push(userID)
SearchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
searchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
} else {
this.contactsList.splice(curPos, 1);
}

2
src/lib/config.ts

File diff suppressed because one or more lines are too long

461
src/lib/lottieLoader copy.ts

@ -0,0 +1,461 @@ @@ -0,0 +1,461 @@
import { isInDOM } from "./utils";
let convert = (value: number) => {
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
};
type RLottiePlayerListeners = 'firstFrame' | 'enterFrame';
export class RLottiePlayer {
public static reqId = 0;
public reqId = 0;
public curFrame: number;
public worker: QueryableWorker;
public el: HTMLElement;
public width: number;
public height: number;
public listeners: Partial<{
[k in RLottiePlayerListeners]: (res: any) => void
}> = {};
public listenerResults: Partial<{
[k in RLottiePlayerListeners]: any
}> = {};
public canvas: HTMLCanvasElement;
public context: CanvasRenderingContext2D;
public paused = false;
public direction = 1;
public speed = 1;
public autoplay = true;
constructor({el, width, height, worker}: {
el: HTMLElement,
width: number,
height: number,
worker: QueryableWorker
}) {
this.reqId = ++RLottiePlayer['reqId'];
this.el = el;
this.width = width;
this.height = height;
this.worker = worker;
}
public addListener(name: RLottiePlayerListeners, callback: (res?: any) => void) {
if(this.listenerResults.hasOwnProperty(name)) return Promise.resolve(this.listenerResults[name]);
this.listeners[name] = callback;
}
public setListenerResult(name: RLottiePlayerListeners, value?: any) {
this.listenerResults[name] = value;
if(this.listeners[name]) {
this.listeners[name](value);
}
}
private sendQuery(methodName: string, ...args: any[]) {
this.worker.sendQuery(methodName, this.reqId, ...args);
}
public loadFromData(json: any) {
this.sendQuery('loadFromData', json, this.width, this.height, {
paused: this.paused,
direction: this.direction,
speed: this.speed
});
}
public play() {
this.sendQuery('play');
this.paused = false;
}
public pause() {
this.sendQuery('pause');
this.paused = true;
}
public stop() {
this.sendQuery('stop');
this.paused = true;
}
public restart() {
this.sendQuery('restart');
}
public setSpeed(speed: number) {
this.sendQuery('setSpeed', speed);
}
public setDirection(direction: number) {
this.direction = direction;
this.sendQuery('setDirection', direction);
}
public destroy() {
lottieLoader.onDestroy(this.reqId);
this.sendQuery('destroy');
}
private attachPlayer() {
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
//this.el.appendChild(this.canvas);
this.context = this.canvas.getContext('2d');
}
public renderFrame(frame: Uint8ClampedArray, frameNo: number) {
if(!this.listenerResults.hasOwnProperty('firstFrame')) {
this.attachPlayer();
this.el.appendChild(this.canvas);
this.setListenerResult('firstFrame');
}
this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0);
this.setListenerResult('enterFrame', frameNo);
}
}
class QueryableWorker {
private worker: Worker;
private listeners: {[name: string]: (...args: any[]) => void} = {};
constructor(url: string, private defaultListener: (data: any) => void = () => {}, onError?: (error: any) => void) {
this.worker = new Worker(url);
if(onError) {
this.worker.onerror = onError;
}
this.worker.onmessage = (event) => {
if(event.data instanceof Object &&
event.data.hasOwnProperty('queryMethodListener') &&
event.data.hasOwnProperty('queryMethodArguments')) {
this.listeners[event.data.queryMethodListener].apply(this, event.data.queryMethodArguments);
} else {
this.defaultListener.call(this, event.data);
}
}
}
public postMessage(message: any) {
this.worker.postMessage(message);
}
public terminate() {
this.worker.terminate();
}
public addListener(name: string, listener: (...args: any[]) => void) {
this.listeners[name] = listener;
}
public removeListener(name: string) {
delete this.listeners[name];
}
public sendQuery(queryMethod: string, ...args: any[]) {
this.worker.postMessage({
'queryMethod': queryMethod,
'queryMethodArguments': args
});
}
}
class LottieLoader {
public loadPromise: Promise<void>;
public loaded = false;
private static COLORREPLACEMENTS = [
[
[0xf77e41, 0xca907a],
[0xffb139, 0xedc5a5],
[0xffd140, 0xf7e3c3],
[0xffdf79, 0xfbefd6],
],
[
[0xf77e41, 0xaa7c60],
[0xffb139, 0xc8a987],
[0xffd140, 0xddc89f],
[0xffdf79, 0xe6d6b2],
],
[
[0xf77e41, 0x8c6148],
[0xffb139, 0xad8562],
[0xffd140, 0xc49e76],
[0xffdf79, 0xd4b188],
],
[
[0xf77e41, 0x6e3c2c],
[0xffb139, 0x925a34],
[0xffd140, 0xa16e46],
[0xffdf79, 0xac7a52],
]
];
private workersLimit = 4;
private players: {[reqId: number]: RLottiePlayer} = {};
private byGroups: {[group: string]: RLottiePlayer[]} = {};
private workers: QueryableWorker[] = [];
private curWorkerNum = 0;
private observer: IntersectionObserver;
private visible: Set<RLottiePlayer> = new Set();
private debug = true;
constructor() {
this.observer = new IntersectionObserver((entries) => {
for(const entry of entries) {
const target = entry.target;
for(const group in this.byGroups) {
const player = this.byGroups[group].find(p => p.el == target);
if(player) {
if(entry.isIntersecting) {
this.visible.add(player);
if(player.paused) {
player.play();
}
} else {
this.visible.delete(player);
if(!player.paused) {
player.pause();
}
}
break;
}
}
}
});
}
public loadLottieWorkers() {
if(this.loadPromise) return this.loadPromise;
const onFrame = this.onFrame.bind(this);
return this.loadPromise = new Promise((resolve, reject) => {
let remain = this.workersLimit;
for(let i = 0; i < this.workersLimit; ++i) {
const worker = this.workers[i] = new QueryableWorker('rlottie.worker.js');
worker.addListener('ready', () => {
console.log('worker #' + i + ' ready');
worker.addListener('frame', onFrame);
--remain;
if(!remain) {
console.log('workers ready');
resolve();
this.loaded = true;
}
});
}
});
}
private applyReplacements(object: any, toneIndex: number) {
const replacements = LottieLoader.COLORREPLACEMENTS[toneIndex - 2];
const iterateIt = (it: any) => {
for(let smth of it) {
switch(smth.ty) {
case 'st':
case 'fl':
let k = smth.c.k;
let color = convert(k[2]) | (convert(k[1]) << 8) | (convert(k[0]) << 16);
let foundReplacement = replacements.find(p => p[0] == color);
if(foundReplacement) {
k[0] = ((foundReplacement[1] >> 16) & 255) / 255;
k[1] = ((foundReplacement[1] >> 8) & 255) / 255;
k[2] = (foundReplacement[1] & 255) / 255;
}
//console.log('foundReplacement!', foundReplacement, color.toString(16), k);
break;
}
if(smth.hasOwnProperty('it')) {
iterateIt(smth.it);
}
}
};
for(let layer of object.layers) {
if(!layer.shapes) continue;
for(let shape of layer.shapes) {
iterateIt(shape.it);
}
}
}
public async loadAnimationWorker(params: {
container: HTMLElement,
autoplay?: boolean,
animationData: any,
loop?: boolean,
renderer?: string,
width?: number,
height?: number
}, group = '', toneIndex = -1) {
//params.autoplay = false;
if(toneIndex >= 1 && toneIndex <= 5) {
this.applyReplacements(params.animationData, toneIndex);
}
if(!this.loaded) {
await this.loadLottieWorkers();
}
this.observer.observe(params.container);
const width = params.width || parseInt(params.container.style.width);
const height = params.height || parseInt(params.container.style.height);
const player = this.initPlayer(params.container, params.animationData, width, height);
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);
return player;
}
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
const groups = group && false ? [group] : Object.keys(this.byGroups);
if(group && !this.byGroups[group]) {
console.warn('no animation group:', group);
this.byGroups[group] = [];
//return;
}
for(const group of groups) {
const animations = this.byGroups[group];
const length = animations.length;
for(let i = length - 1; i >= 0; --i) {
const player = animations[i];
if(destroy || (!isInDOM(player.el) && player.listenerResults.hasOwnProperty('firstFrame'))) {
//console.log('destroy animation');
player.destroy();
continue;
}
if(blurred) {
if(!player.paused) {
this.debug && console.log('pause animation', player);
player.pause();
}
} else if(player.paused && this.visible.has(player)) {
this.debug && console.log('play animation', player);
player.play();
}
/* if(canvas) {
let c = container.firstElementChild as HTMLCanvasElement;
if(!c) {
console.warn('no canvas element for check!', container, animations[i]);
continue;
}
if(!c.height && !c.width && isElementInViewport(container)) {
//console.log('lottie need resize');
animation.resize();
}
} */
//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;
} */
}
}
}
private onFrame(reqId: number, frameNo: number, frame: Uint8ClampedArray, width: number, height: number) {
const rlPlayer = this.players[reqId];
if(!rlPlayer) {
this.debug && console.warn('onFrame on destroyed player:', reqId, frameNo);
return;
}
rlPlayer.renderFrame(frame, frameNo);
}
public onDestroy(reqId: number) {
let player = this.players[reqId];
for(let group in this.byGroups) {
this.byGroups[group].findAndSplice(p => p == player);
}
delete this.players[player.reqId];
this.observer.unobserve(player.el);
this.visible.delete(player);
}
public destroyWorkers() {
this.workers.forEach((worker, idx) => {
worker.terminate();
console.log('worker #' + idx + ' terminated');
});
console.log('workers destroyed');
this.workers.length = 0;
}
private initPlayer(el: HTMLElement, json: any, width: number, height: number) {
const rlPlayer = new RLottiePlayer({
el,
width,
height,
worker: this.workers[this.curWorkerNum++]
});
this.players[rlPlayer.reqId] = rlPlayer;
if(this.curWorkerNum >= this.workers.length) {
this.curWorkerNum = 0;
}
rlPlayer.loadFromData(json);
return rlPlayer;
}
}
const lottieLoader = new LottieLoader();
(window as any).LottieLoader = lottieLoader;
export default lottieLoader;

554
src/lib/lottieLoader.ts

@ -1,25 +1,253 @@ @@ -1,25 +1,253 @@
//import { isInDOM } from "./utils";
import LottiePlayer, { AnimationConfigWithPath, AnimationConfigWithData, AnimationItem } from "lottie-web/build/player/lottie.d";
import { isInDOM } from "./utils";
let convert = (value: number) => {
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
};
type RLottiePlayerListeners = 'enterFrame' | 'ready';
export class RLottiePlayer {
public static reqId = 0;
public reqId = 0;
public curFrame: number;
public frameCount: number;
public fps: number;
public worker: QueryableWorker;
public width: number;
public height: number;
public listeners: Partial<{
[k in RLottiePlayerListeners]: (res: any) => void
}> = {};
public listenerResults: Partial<{
[k in RLottiePlayerListeners]: any
}> = {};
public el: HTMLElement;
public canvas: HTMLCanvasElement;
public context: CanvasRenderingContext2D;
public paused = true;
public direction = 1;
public speed = 1;
public autoplay = true;
public loop = true;
private frInterval: number;
private frThen: number;
private rafId: number;
private playedTimes = 0;
constructor({el, width, height, worker}: {
el: HTMLElement,
width: number,
height: number,
worker: QueryableWorker
}) {
this.reqId = ++RLottiePlayer['reqId'];
this.el = el;
this.width = width;
this.height = height;
this.worker = worker;
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.context = this.canvas.getContext('2d');
}
public addListener(name: RLottiePlayerListeners, callback: (res?: any) => void) {
if(this.listenerResults.hasOwnProperty(name)) return Promise.resolve(this.listenerResults[name]);
this.listeners[name] = callback;
}
public setListenerResult(name: RLottiePlayerListeners, value?: any) {
this.listenerResults[name] = value;
if(this.listeners[name]) {
this.listeners[name](value);
}
}
public sendQuery(methodName: string, ...args: any[]) {
this.worker.sendQuery(methodName, this.reqId, ...args);
}
public loadFromData(json: any) {
this.sendQuery('loadFromData', json, this.width, this.height, {
paused: this.paused,
direction: this.direction,
speed: this.speed
});
}
public play() {
if(!this.paused) return;
this.paused = false;
this.setMainLoop();
}
public pause() {
if(this.paused) return;
this.paused = true;
window.cancelAnimationFrame(this.rafId);
}
public stop() {
this.pause();
this.curFrame = this.direction == 1 ? 0 : this.frameCount;
this.sendQuery('renderFrame', this.curFrame);
}
public restart() {
this.stop();
this.play();
}
public setSpeed(speed: number) {
this.speed = speed;
if(!this.paused) {
this.setMainLoop();
}
}
public setDirection(direction: number) {
this.direction = direction;
if(!this.paused) {
this.setMainLoop();
}
}
public destroy() {
lottieLoader.onDestroy(this.reqId);
this.pause();
this.sendQuery('destroy');
}
public renderFrame(frame: Uint8ClampedArray, frameNo: number) {
this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0);
this.setListenerResult('enterFrame', frameNo);
}
private mainLoop(method: RLottiePlayer['mainLoopForwards'] | RLottiePlayer['mainLoopBackwards']) {
let r = () => {
if(this.paused) {
return;
}
const now = Date.now(), delta = now - this.frThen;
if(delta > this.frInterval) {
this.frThen = now - (delta % this.frInterval);
const canContinue = method();
if(!canContinue && !this.loop && this.autoplay) {
this.autoplay = false;
}
}
this.rafId = window.requestAnimationFrame(r);
};
//this.rafId = window.requestAnimationFrame(r);
r();
}
private mainLoopForwards() {
this.sendQuery('renderFrame', this.curFrame++);
if(this.curFrame >= this.frameCount) {
this.playedTimes++;
if(!this.loop) return false;
this.curFrame = 0;
}
return true;
};
private mainLoopBackwards() {
this.sendQuery('renderFrame', this.curFrame--);
if(this.curFrame < 0) {
this.playedTimes++;
if(!this.loop) return false;
this.curFrame = this.frameCount - 1;
}
return true;
};
public setMainLoop() {
window.cancelAnimationFrame(this.rafId);
this.frInterval = 1000 / this.fps / this.speed;
this.frThen = Date.now();
//console.trace('setMainLoop', this.frInterval, this.direction, this);
const method = (this.direction == 1 ? this.mainLoopForwards : this.mainLoopBackwards).bind(this);
this.mainLoop(method);
};
}
class QueryableWorker {
private worker: Worker;
private listeners: {[name: string]: (...args: any[]) => void} = {};
constructor(url: string, private defaultListener: (data: any) => void = () => {}, onError?: (error: any) => void) {
this.worker = new Worker(url);
if(onError) {
this.worker.onerror = onError;
}
this.worker.onmessage = (event) => {
if(event.data instanceof Object &&
event.data.hasOwnProperty('queryMethodListener') &&
event.data.hasOwnProperty('queryMethodArguments')) {
this.listeners[event.data.queryMethodListener].apply(this, event.data.queryMethodArguments);
} else {
this.defaultListener.call(this, event.data);
}
}
}
public postMessage(message: any) {
this.worker.postMessage(message);
}
public terminate() {
this.worker.terminate();
}
public addListener(name: string, listener: (...args: any[]) => void) {
this.listeners[name] = listener;
}
public removeListener(name: string) {
delete this.listeners[name];
}
public sendQuery(queryMethod: string, ...args: any[]) {
this.worker.postMessage({
'queryMethod': queryMethod,
'queryMethodArguments': args
});
}
}
class LottieLoader {
public lottie: /* any */ typeof LottiePlayer = null;
private animations: {
[group: string]: {
animation: /* any */AnimationItem,
container: HTMLDivElement,
paused: boolean,
autoplay: boolean,
canvas: boolean
}[]
} = {};
private debug = false;
public loaded: Promise<void>;
private lastTimeLoad = 0;
private waitingTimeouts = 0;
public loadPromise: Promise<void>;
public loaded = false;
private static COLORREPLACEMENTS = [
[
[0xf77e41, 0xca907a],
@ -48,78 +276,69 @@ class LottieLoader { @@ -48,78 +276,69 @@ class LottieLoader {
[0xffd140, 0xa16e46],
[0xffdf79, 0xac7a52],
]
];
public loadLottie() {
if(this.loaded) return this.loaded;
];
private workersLimit = 4;
private players: {[reqId: number]: RLottiePlayer} = {};
private byGroups: {[group: string]: RLottiePlayer[]} = {};
private workers: QueryableWorker[] = [];
private curWorkerNum = 0;
private observer: IntersectionObserver;
private visible: Set<RLottiePlayer> = new Set();
private debug = true;
constructor() {
this.observer = new IntersectionObserver((entries) => {
for(const entry of entries) {
const target = entry.target;
for(const group in this.byGroups) {
const player = this.byGroups[group].find(p => p.el == target);
if(player) {
if(entry.isIntersecting) {
this.visible.add(player);
this.checkAnimation(player, false);
} else {
this.visible.delete(player);
this.checkAnimation(player, true);
}
return this.loaded = new Promise((resolve, reject) => {
(window as any).lottieLoaded = () => {
console.log('lottie loaded');
this.lottie = (window as any).lottie;
resolve();
};
let sc = document.createElement('script');
sc.src = 'npm.lottie-web.chunk.js';
sc.async = true;
sc.onload = (window as any).lottieLoaded;
document.body.appendChild(sc);
break;
}
}
}
});
}
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
let groups = group ? [group] : Object.keys(this.animations);
public loadLottieWorkers() {
if(this.loadPromise) return this.loadPromise;
if(group && !this.animations[group]) {
console.warn('no animation group:', group);
this.animations[group] = [];
//return;
}
const onFrame = this.onFrame.bind(this);
const onPlayerLoaded = this.onPlayerLoaded.bind(this);
for(let group of groups) {
let animations = this.animations[group];
return this.loadPromise = new Promise((resolve, reject) => {
let remain = this.workersLimit;
for(let i = 0; i < this.workersLimit; ++i) {
const worker = this.workers[i] = new QueryableWorker('rlottie.worker.js');
let length = animations.length;
for(let i = length - 1; i >= 0; --i) {
let {animation, container, paused, autoplay, canvas} = animations[i];
worker.addListener('ready', () => {
console.log('worker #' + i + ' ready');
if(destroy && !container.parentElement/* !isInDOM(container) */) {
this.debug && console.log('destroy animation');
animation.destroy();
animations.splice(i, 1);
continue;
}
worker.addListener('frame', onFrame);
worker.addListener('loaded', onPlayerLoaded);
/* if(canvas) {
let c = container.firstElementChild as HTMLCanvasElement;
if(!c) {
console.warn('no canvas element for check!', container, animations[i]);
continue;
}
if(!c.height && !c.width && isElementInViewport(container)) {
//console.log('lottie need resize');
animation.resize();
}
} */
if(!autoplay) continue;
/* if(blurred || !isElementInViewport(container)) {
if(!paused) {
this.debug && console.log('pause animation', isElementInViewport(container), container);
animation.pause();
animations[i].paused = true;
--remain;
if(!remain) {
console.log('workers ready');
resolve();
this.loaded = true;
}
} else if(paused) {
this.debug && console.log('play animation', container);
animation.play();
animations[i].paused = false;
} */
});
}
}
});
}
private applyReplacements(object: any, toneIndex: number) {
@ -159,81 +378,156 @@ class LottieLoader { @@ -159,81 +378,156 @@ class LottieLoader {
}
}
public async loadAnimation(params: /* any */AnimationConfigWithPath & AnimationConfigWithData, group = '', toneIndex = -1) {
//params.autoplay = false;
//if(group != 'auth') {
//params.renderer = 'canvas';
params.renderer = 'svg';
//}
public async loadAnimationWorker(params: {
container: HTMLElement,
autoplay?: boolean,
animationData: any,
loop?: boolean,
width?: number,
height?: number
}, group = '', toneIndex = -1) {
params.autoplay = true;
if(toneIndex >= 1 && toneIndex <= 5) {
this.applyReplacements(params.animationData, toneIndex);
}
let rendererSettings = {
//context: context, // the canvas context
//preserveAspectRatio: 'xMinYMin slice', // Supports the same options as the svg element's preserveAspectRatio property
clearCanvas: true,
progressiveLoad: true, // Boolean, only svg renderer, loads dom elements when needed. Might speed up initialization for large number of elements.
hideOnTransparent: true, //Boolean, only svg renderer, hides elements when opacity reaches 0 (defaults to true),
};
if(params.rendererSettings) {
params.rendererSettings = Object.assign(params.rendererSettings, rendererSettings);
} else {
params.rendererSettings = rendererSettings;
if(!this.loaded) {
await this.loadLottieWorkers();
}
const width = params.width || parseInt(params.container.style.width);
const height = params.height || parseInt(params.container.style.height);
const player = this.initPlayer(params.container, params.animationData, width, height);
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);
return player;
}
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
const groups = group && false ? [group] : Object.keys(this.byGroups);
if(group && !this.byGroups[group]) {
console.warn('no animation group:', group);
this.byGroups[group] = [];
//return;
}
for(const group of groups) {
const animations = this.byGroups[group];
animations.forEach(player => {
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;
} */
});
}
}
if(!this.lottie) {
if(!this.loaded) this.loadLottie();
await this.loaded;
public checkAnimation(player: RLottiePlayer, blurred = false, destroy = false) {
if(destroy || (!isInDOM(player.el) && player.listenerResults.hasOwnProperty('ready'))) {
//console.log('destroy animation');
player.destroy();
return;
}
this.lottie.setQuality('low');
//this.lottie.setQuality(10);
if(blurred) {
if(!player.paused) {
this.debug && console.log('pause animation', player);
player.pause();
}
} else if(player.paused && this.visible.has(player) && player.autoplay) {
this.debug && console.log('play animation', player);
player.play();
}
}
let time = Date.now();
let diff = time - this.lastTimeLoad;
let delay = 150;
if(diff < delay) {
delay *= ++this.waitingTimeouts;
console.log('lottieloader delay:', delay);
//await new Promise((resolve) => setTimeout(resolve, delay));
this.waitingTimeouts--;
private onPlayerLoaded(reqId: number, frameCount: number, fps: number) {
const rlPlayer = this.players[reqId];
if(!rlPlayer) {
this.debug && console.warn('onPlayerLoaded on destroyed player:', reqId, frameCount);
return;
}
let animation = this.lottie.loadAnimation(params);
rlPlayer.el.appendChild(rlPlayer.canvas);
rlPlayer.curFrame = rlPlayer.direction == 1 ? 0 : frameCount - 1;
rlPlayer.frameCount = frameCount;
rlPlayer.fps = fps;
rlPlayer.sendQuery('renderFrame', 0);
rlPlayer.setListenerResult('ready');
this.observer.observe(rlPlayer.el);
}
this.lastTimeLoad = Date.now();
private onFrame(reqId: number, frameNo: number, frame: Uint8ClampedArray) {
const rlPlayer = this.players[reqId];
if(!rlPlayer) {
this.debug && console.warn('onFrame on destroyed player:', reqId, frameNo);
return;
}
if(!this.animations[group]) this.animations[group] = [];
this.animations[group].push({
animation,
container: params.container as HTMLDivElement,
paused: !params.autoplay,
autoplay: params.autoplay,
canvas: false//params.renderer == 'canvas'
});
rlPlayer.renderFrame(frame, frameNo);
}
if(params.autoplay) {
this.checkAnimations();
public onDestroy(reqId: number) {
let player = this.players[reqId];
for(let group in this.byGroups) {
this.byGroups[group].findAndSplice(p => p == player);
}
return animation;
delete this.players[player.reqId];
this.observer.unobserve(player.el);
this.visible.delete(player);
}
public destroyWorkers() {
this.workers.forEach((worker, idx) => {
worker.terminate();
console.log('worker #' + idx + ' terminated');
});
console.log('workers destroyed');
this.workers.length = 0;
}
public getAnimation(el: HTMLElement, group = '') {
let groups = group ? [group] : Object.keys(this.animations);
//console.log('getAnimation', groups, this.animations);
for(let group of groups) {
let animations = this.animations[group];
private initPlayer(el: HTMLElement, json: any, width: number, height: number) {
const rlPlayer = new RLottiePlayer({
el,
width,
height,
worker: this.workers[this.curWorkerNum++]
});
let animation = animations.find(a => a.container === el);
if(animation) return animation.animation;
this.players[rlPlayer.reqId] = rlPlayer;
if(this.curWorkerNum >= this.workers.length) {
this.curWorkerNum = 0;
}
return null;
rlPlayer.loadFromData(json);
return rlPlayer;
}
}

110
src/lib/searchIndexManager.ts

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
import Config from './config';
class SearchIndexManager {
public static badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g;
public static trimRe = /^\s+|\s$/g;
public createIndex() {
return {
shortIndexes: {},
fullTexts: {}
};
}
public cleanSearchText(text: string, latinize = true) {
const hasTag = text.charAt(0) == '%';
text = text.replace(SearchIndexManager['badCharsRe'], '').replace(SearchIndexManager['trimRe'], '');
if(latinize) {
text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
const latinizeCh = Config.LatinizeMap[ch];
return latinizeCh !== undefined ? latinizeCh : ch;
});
}
text = text.toLowerCase();
if(hasTag) {
text = '%' + text;
}
return text;
}
public cleanUsername(username: string) {
return username && username.toLowerCase() || '';
}
public indexObject(id: number, searchText: string, searchIndex: any) {
if(searchIndex.fullTexts[id] !== undefined) {
return false;
}
searchText = this.cleanSearchText(searchText);
if(!searchText.length) {
return false;
}
const shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText;
searchText.split(' ').forEach((searchWord) => {
let len = Math.min(searchWord.length, 3),
wordPart, i;
for(i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i);
if(shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id);
}
}
});
}
public search(query: string, searchIndex: any) {
const shortIndexes = searchIndex.shortIndexes;
const fullTexts = searchIndex.fullTexts;
query = this.cleanSearchText(query);
const queryWords = query.split(' ');
let foundObjs: any = false,
newFoundObjs: any, i: number;
let j: number, searchText: string;
let found: boolean;
for(i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if(!newFoundObjs) {
foundObjs = [];
break;
}
if(foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs;
}
}
newFoundObjs = {};
for(j = 0; j < foundObjs.length; j++) {
found = true;
searchText = fullTexts[foundObjs[j]];
for(i = 0; i < queryWords.length; i++) {
if(searchText.indexOf(queryWords[i]) == -1) {
found = false;
break;
}
}
if(found) {
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs;
}
}
export default new SearchIndexManager();

276
src/lib/utils.js

@ -9,18 +9,6 @@ export function dT () { @@ -9,18 +9,6 @@ export function dT () {
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
}
export function checkClick(e, noprevent) {
if(e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) {
return true;
}
if(!noprevent) {
e.preventDefault();
}
return false;
}
export function isInDOM(element, parentNode) {
if(!element) {
return false;
@ -62,60 +50,6 @@ export function cancelEvent (event) { @@ -62,60 +50,6 @@ export function cancelEvent (event) {
return false;
}
export function setFieldSelection (field, from, to) {
field = $(field)[0]
try {
field.focus()
if (from === undefined || from === false) {
from = field.value.length
}
if (to === undefined || to === false) {
to = from
}
if (field.createTextRange) {
var range = field.createTextRange()
range.collapse(true)
range.moveEnd('character', to)
range.moveStart('character', from)
range.select()
}
else if (field.setSelectionRange) {
field.setSelectionRange(from, to)
}
} catch(e) {}
}
export function getFieldSelection (field) {
if (field.selectionStart) {
return field.selectionStart
}
else if (!document.selection) {
return 0
}
var c = '\x01'
var sel = document.selection.createRange()
var txt = sel.text
var dup = sel.duplicate()
var len = 0
try {
dup.moveToElementText(field)
} catch(e) {
return 0
}
sel.text = txt + c
len = dup.text.indexOf(c)
sel.moveStart('character', -1)
sel.text = ''
// if (browser.msie && len == -1) {
// return field.value.length
// }
return len
}
export function getRichValue (field) {
if (!field) {
return ''
@ -152,42 +86,6 @@ export function placeCaretAtEnd(el) { @@ -152,42 +86,6 @@ export function placeCaretAtEnd(el) {
}
}
export function getRichValueWithCaret (field) {
if (!field) {
return []
}
var lines = []
var line = []
var sel = window.getSelection ? window.getSelection() : false
var selNode
var selOffset
if (sel && sel.rangeCount) {
var range = sel.getRangeAt(0)
/* if (range.startContainer &&
range.startContainer == range.endContainer &&
range.startOffset == range.endOffset) { */
selNode = range.startContainer
selOffset = range.startOffset
//}
}
getRichElementValue(field, lines, line, selNode, selOffset)
if (line.length) {
lines.push(line.join(''))
}
var value = lines.join('\n')
var caretPos = value.indexOf('\x01')
if (caretPos != -1) {
value = value.substr(0, caretPos) + value.substr(caretPos + 1)
}
value = value.replace(/\u00A0/g, ' ')
return [value, caretPos]
}
export function getRichElementValue (node, lines, line, selNode, selOffset) {
if (node.nodeType == 3) { // TEXT
if (selNode === node) {
@ -230,50 +128,6 @@ export function getRichElementValue (node, lines, line, selNode, selOffset) { @@ -230,50 +128,6 @@ export function getRichElementValue (node, lines, line, selNode, selOffset) {
}
}
export function setRichFocus (field, selectNode, noCollapse) {
field.focus()
if (selectNode &&
selectNode.parentNode == field &&
!selectNode.nextSibling &&
!noCollapse) {
field.removeChild(selectNode)
selectNode = null
}
if (window.getSelection && document.createRange) {
var range = document.createRange()
if (selectNode) {
range.selectNode(selectNode)
} else {
range.selectNodeContents(field)
}
if (!noCollapse) {
range.collapse(false)
}
var sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
}
else if (document.body.createTextRange !== undefined) {
var textRange = document.body.createTextRange()
textRange.moveToElementText(selectNode || field)
if (!noCollapse) {
textRange.collapse(false)
}
textRange.select()
}
}
export function getSelectedText() {
var sel = (
window.getSelection && window.getSelection() ||
document.getSelection && document.getSelection() ||
document.selection && document.selection.createRange().text || ''
).toString().replace(/^\s+|\s+$/g, '')
return sel
}
/* if (Config.Modes.animations &&
typeof window.requestAnimationFrame == 'function') {
window.onAnimationFrameCallback = function (cb) {
@ -528,20 +382,6 @@ export function listMergeSorted (list1, list2) { @@ -528,20 +382,6 @@ export function listMergeSorted (list1, list2) {
return result
}
export function listUniqSorted (list) {
list = list || []
var resultList = []
var prev = false
for (var i = 0; i < list.length; i++) {
if (list[i] !== prev) {
resultList.push(list[i])
}
prev = list[i]
}
return resultList
}
// credits to https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js
export function escapeRegExp(str) {
return str
@ -657,119 +497,3 @@ export function getEmojiToneIndex(input) { @@ -657,119 +497,3 @@ export function getEmojiToneIndex(input) {
let match = input.match(/[\uDFFB-\uDFFF]/);
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}
//var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g,
var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g,
trimRe = /^\s+|\s$/g
function createIndex () {
return {
shortIndexes: {},
fullTexts: {}
}
}
function cleanSearchText(text, latinize = true) {
var hasTag = text.charAt(0) == '%';
text = text.replace(badCharsRe, '').replace(trimRe, '');
if(latinize) {
text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
var latinizeCh = Config.LatinizeMap[ch];
return latinizeCh !== undefined ? latinizeCh : ch;
});
}
text = text.toLowerCase();
if(hasTag) {
text = '%' + text;
}
return text;
}
function cleanUsername(username) {
return username && username.toLowerCase() || '';
}
function indexObject(id, searchText, searchIndex) {
if(searchIndex.fullTexts[id] !== undefined) {
return false;
}
searchText = cleanSearchText(searchText);
if(!searchText.length) {
return false;
}
var shortIndexes = searchIndex.shortIndexes;
searchIndex.fullTexts[id] = searchText;
searchText.split(' ').forEach((searchWord) => {
var len = Math.min(searchWord.length, 3),
wordPart, i;
for(i = 1; i <= len; i++) {
wordPart = searchWord.substr(0, i);
if(shortIndexes[wordPart] === undefined) {
shortIndexes[wordPart] = [id];
} else {
shortIndexes[wordPart].push(id);
}
}
});
}
function search(query, searchIndex) {
var shortIndexes = searchIndex.shortIndexes;
var fullTexts = searchIndex.fullTexts;
query = cleanSearchText(query);
var queryWords = query.split(' ');
var foundObjs = false,
newFoundObjs, i;
var j, searchText;
var found;
for(i = 0; i < queryWords.length; i++) {
newFoundObjs = shortIndexes[queryWords[i].substr(0, 3)];
if(!newFoundObjs) {
foundObjs = [];
break;
}
if(foundObjs === false || foundObjs.length > newFoundObjs.length) {
foundObjs = newFoundObjs;
}
}
newFoundObjs = {};
for(j = 0; j < foundObjs.length; j++) {
found = true;
searchText = fullTexts[foundObjs[j]];
for(i = 0; i < queryWords.length; i++) {
if(searchText.indexOf(queryWords[i]) == -1) {
found = false;
break;
}
}
if(found) {
newFoundObjs[foundObjs[j]] = true;
}
}
return newFoundObjs;
}
let SearchIndexManager = {
createIndex: createIndex,
indexObject: indexObject,
cleanSearchText: cleanSearchText,
cleanUsername: cleanUsername,
search: search
};
//window.SearchIndexManager = SearchIndexManager;
export {SearchIndexManager};
//})(window)

76
src/pages/pageAuthCode.ts

@ -2,7 +2,7 @@ import pageSignIn from './pageSignIn'; @@ -2,7 +2,7 @@ import pageSignIn from './pageSignIn';
import pageSignUp from './pageSignUp';
import pageIm from './pageIm';
import pagePassword from './pagePassword';
import LottieLoader from '../lib/lottieLoader';
import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
//import CryptoWorker from '../lib/crypto/cryptoworker';
//import apiManager from '../lib/mtproto/apiManager';
import apiManager from '../lib/mtproto/mtprotoworker';
@ -29,11 +29,8 @@ let sentTypeElement: HTMLParagraphElement = null; @@ -29,11 +29,8 @@ let sentTypeElement: HTMLParagraphElement = null;
let onFirstMount = (): Promise<any> => {
let needFrame = 0, lastLength = 0;
let animation: /* AnimationItem */any;
let idleAnimation: any;
let mTrackingSvg: SVGSVGElement;
let mIdleSvg: SVGSVGElement;
let animation: RLottiePlayer;
let idleAnimation: RLottiePlayer;
const CODELENGTH = authCode.type.length;
@ -41,6 +38,8 @@ let onFirstMount = (): Promise<any> => { @@ -41,6 +38,8 @@ let onFirstMount = (): Promise<any> => {
const codeInputLabel = codeInput.nextElementSibling as HTMLLabelElement;
const editButton = page.pageEl.querySelector('.phone-edit') as HTMLElement;
codeInput.focus();
if(EDITONSAMEPAGE) {
let editable = false;
let changePhonePromise: Promise<unknown>;
@ -209,17 +208,17 @@ let onFirstMount = (): Promise<any> => { @@ -209,17 +208,17 @@ let onFirstMount = (): Promise<any> => {
let frame: number;
if(length) {
frame = Math.round((length > max ? max : length) * (165 / max) + 11.33);
frame = Math.round(Math.min(max, length) * (165 / max) + 11.33);
mIdleSvg.style.display = 'none';
mTrackingSvg.style.display = '';
idleAnimation.canvas.style.display = 'none';
animation.canvas.style.display = '';
} else {
frame = 0;
}
//animation.playSegments([1, 2]);
let direction = needFrame > frame ? -1 : 1;
//console.log('keydown', length, frame, direction);
console.log('keydown', length, frame, direction);
animation.setDirection(direction);
if(needFrame != 0 && frame == 0) {
@ -235,69 +234,64 @@ let onFirstMount = (): Promise<any> => { @@ -235,69 +234,64 @@ let onFirstMount = (): Promise<any> => {
//animation.goToAndStop(length / max * );
});
let imageDiv = page.pageEl.querySelector('.auth-image');
let imageDiv = page.pageEl.querySelector('.auth-image') as HTMLDivElement;
return Promise.all([
LottieLoader.loadLottie(),
LottieLoader.loadLottieWorkers(),
fetch('assets/img/TwoFactorSetupMonkeyIdle.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimation({
.then(str => LottieLoader.loadAnimationWorker({
container: imageDiv,
renderer: 'svg',
loop: true,
autoplay: true,
animationData: JSON.parse(str),
rendererSettings: {
className: 'monkey-idle'
}
width: 166,
height: 166
}))
.then(_animation => {
idleAnimation = _animation;
mIdleSvg = imageDiv.querySelector('.monkey-idle');
.then(animation => {
idleAnimation = animation;
}),
fetch('assets/img/TwoFactorSetupMonkeyTracking.tgs')
/* false && */fetch('assets/img/TwoFactorSetupMonkeyTracking.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimation({
.then(str => LottieLoader.loadAnimationWorker({
container: imageDiv,
renderer: 'svg',
loop: false,
autoplay: false,
animationData: JSON.parse(str),
rendererSettings: {
className: 'monkey-tracking'
}
width: 166,
height: 166
}))
.then(_animation => {
animation = _animation;
animation.setSpeed(1);
//console.log(animation.getDuration(), animation.getDuration(true));
mTrackingSvg = imageDiv.querySelector('.monkey-tracking');
if(!codeInput.value.length) {
mTrackingSvg.style.display = 'none';
animation.canvas.style.display = 'none';
}
animation.addEventListener('enterFrame', (e: any) => {
//console.log('enterFrame', e, needFrame);
let currentFrame = Math.round(e.currentTime);
animation.addListener('enterFrame', currentFrame => {
console.log('enterFrame', currentFrame, needFrame);
//let currentFrame = Math.round(e.currentTime);
if((e.direction == 1 && currentFrame >= needFrame) ||
(e.direction == -1 && currentFrame <= needFrame)) {
if((animation.direction == 1 && currentFrame >= needFrame) ||
(animation.direction == -1 && currentFrame <= needFrame)) {
animation.setSpeed(1);
animation.pause();
}
if(currentFrame == 0 && needFrame == 0 && mIdleSvg) {
mTrackingSvg.style.display = 'none';
mIdleSvg.style.display = '';
idleAnimation.stop();
idleAnimation.play();
if(currentFrame == 0 && needFrame == 0) {
animation.curFrame = 0;
if(idleAnimation) {
animation.canvas.style.display = 'none';
idleAnimation.canvas.style.display = '';
idleAnimation.restart();
}
}
});
//console.log(animation.getDuration(), animation.getDuration(true));
})
]);
};

24
src/pages/pagePassword.ts

@ -3,14 +3,14 @@ import pageIm from './pageIm'; @@ -3,14 +3,14 @@ import pageIm from './pageIm';
//import apiManager from '../lib/mtproto/apiManager';
import { putPreloader } from '../components/misc';
import LottieLoader from '../lib/lottieLoader';
import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
//import passwordManager from '../lib/mtproto/passwordManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page';
let onFirstMount = (): Promise<any> => {
let needFrame = 0;
let animation: /* AnimationItem */any = undefined;
let animation: RLottiePlayer;
let passwordVisible = false;
@ -91,29 +91,29 @@ let onFirstMount = (): Promise<any> => { @@ -91,29 +91,29 @@ let onFirstMount = (): Promise<any> => {
}); */
return Promise.all([
LottieLoader.loadLottie(),
LottieLoader.loadLottieWorkers(),
fetch('assets/img/TwoFactorSetupMonkeyClose.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimation({
.then(str => LottieLoader.loadAnimationWorker({
container: page.pageEl.querySelector('.auth-image'),
renderer: 'svg',
loop: false,
autoplay: false,
animationData: JSON.parse(str)
animationData: JSON.parse(str),
width: 166,
height: 166
}))
.then(_animation => {
animation = _animation;
animation.addEventListener('enterFrame', (e: any) => {
animation.addListener('enterFrame', currentFrame => {
//console.log('enterFrame', e, needFrame);
let currentFrame = Math.round(e.currentTime);
if((e.direction == 1 && currentFrame >= needFrame) ||
(e.direction == -1 && currentFrame <= needFrame)) {
if((animation.direction == 1 && currentFrame >= needFrame) ||
(animation.direction == -1 && currentFrame <= needFrame)) {
animation.setSpeed(1);
animation.pause();
}
}
});
needFrame = 49;

171
src/pages/pageSignQR.ts

@ -56,6 +56,7 @@ let onFirstMount = async() => { @@ -56,6 +56,7 @@ let onFirstMount = async() => {
page.pageEl.querySelector('.a-qr').addEventListener('click', () => {
pageSignIn.mount();
stop = true;
});
const results = await Promise.all([
@ -66,98 +67,112 @@ let onFirstMount = async() => { @@ -66,98 +67,112 @@ let onFirstMount = async() => {
let stop = false;
document.addEventListener('user_auth', () => {
stop = true;
cachedPromise = null;
}, {once: true});
let options: {dcID?: number} = {};
let options: {dcID?: number, ignoreErrors: true} = {ignoreErrors: true};
let prevToken: Uint8Array;
do {
if(stop) {
break;
}
try {
let loginToken: LoginToken | LoginTokenMigrateTo | LoginTokenSuccess = await apiManager.invokeApi('auth.exportLoginToken', {
api_id: App.id,
api_hash: App.hash,
except_ids: []
}/* , options */);
if(loginToken._ == 'auth.loginTokenMigrateTo') {
if(!options.dcID) {
options.dcID = loginToken.dc_id;
apiManager.setBaseDcID(loginToken.dc_id);
//continue;
}
loginToken = await apiManager.invokeApi('auth.importLoginToken', {
token: loginToken.token
}, options) as LoginToken;
}
return async() => {
stop = false;
if(loginToken._ == 'auth.loginTokenSuccess') {
let authorization = loginToken.authorization;
apiManager.setUserAuth({
id: authorization.user.id
});
pageIm.mount();
do {
if(stop) {
break;
}
/* // to base64
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */
if(!prevToken || !bytesCmp(prevToken, loginToken.token)) {
prevToken = loginToken.token;
let encoded = bytesToBase64(loginToken.token);
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
imageDiv.innerHTML = '';
const qrCode = new QRCodeStyling({
width: 166,
height: 166,
data: url,
image: "assets/img/logo_padded.svg",
dotsOptions: {
color: "#000000",
type: "rounded"
},
imageOptions: {
imageSize: .75
},
backgroundOptions: {
color: "#ffffff"
},
qrOptions: {
errorCorrectionLevel: "L"
try {
let loginToken: LoginToken | LoginTokenMigrateTo | LoginTokenSuccess = await apiManager.invokeApi('auth.exportLoginToken', {
api_id: App.id,
api_hash: App.hash,
except_ids: []
}, {ignoreErrors: true});
if(loginToken._ == 'auth.loginTokenMigrateTo') {
if(!options.dcID) {
options.dcID = loginToken.dc_id;
apiManager.setBaseDcID(loginToken.dc_id);
//continue;
}
});
qrCode.append(imageDiv);
}
let timestamp = Date.now() / 1000;
let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset;
await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0));
} catch(err) {
switch(err.type) {
case 'SESSION_PASSWORD_NEEDED':
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED');
err.handled = true;
pagePassword.mount();
break;
default:
console.error('pageSignQR: default error:', err);
loginToken = await apiManager.invokeApi('auth.importLoginToken', {
token: loginToken.token
}, options) as LoginToken;
}
if(loginToken._ == 'auth.loginTokenSuccess') {
let authorization = loginToken.authorization;
apiManager.setUserAuth({
id: authorization.user.id
});
pageIm.mount();
break;
}
/* // to base64
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */
if(!prevToken || !bytesCmp(prevToken, loginToken.token)) {
prevToken = loginToken.token;
let encoded = bytesToBase64(loginToken.token);
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
imageDiv.innerHTML = '';
const qrCode = new QRCodeStyling({
width: 166,
height: 166,
data: url,
image: "assets/img/logo_padded.svg",
dotsOptions: {
color: "#000000",
type: "rounded"
},
imageOptions: {
imageSize: .75
},
backgroundOptions: {
color: "#ffffff"
},
qrOptions: {
errorCorrectionLevel: "L"
}
});
qrCode.append(imageDiv);
}
let timestamp = Date.now() / 1000;
let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset;
await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0));
} catch(err) {
switch(err.type) {
case 'SESSION_PASSWORD_NEEDED':
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED');
err.handled = true;
pagePassword.mount();
stop = true;
cachedPromise = null;
break;
default:
console.error('pageSignQR: default error:', err);
break;
}
}
}
} while(true);
} while(true);
};
};
let cachedPromise: Promise<() => Promise<void>>;
const page = new Page('page-signQR', true, () => {
onFirstMount();
return cachedPromise;
}, () => {
console.log('onMount');
if(!cachedPromise) cachedPromise = onFirstMount();
cachedPromise.then(func => {
func();
});
});
export default page;

8
src/pages/pagesManager.ts

@ -23,12 +23,8 @@ class PagesManager { @@ -23,12 +23,8 @@ class PagesManager {
this.selectTab(id);
// это нужно чтобы ресайзнуть канвас (из-за скрытого рендера будет 0х0)
if(this.pageID != -1) {
lottieLoader.loadLottie().then(() => {
// @ts-ignore
lottieLoader.lottie.resize();
});
if(this.pageID != -1 && id > 1) {
lottieLoader.loadLottieWorkers();
}
this.pageID = id;

38
src/scss/partials/_chatBubble.scss

@ -433,10 +433,10 @@ @@ -433,10 +433,10 @@
max-width: 451px;
max-height: none;
> div {
.album-item {
background-color: #000;
background-size: cover;
background-position: center center;
/* background-position: center center; */
/* flex: 1 0 auto; */
max-width: 100%;
cursor: pointer;
@ -555,6 +555,38 @@ @@ -555,6 +555,38 @@
}
}
&.is-square-photo {
.bubble__container {
width: fit-content;
}
.box.web {
.quote {
display: flex;
}
.preview {
-webkit-box-ordinal-group: 3;
order: 2;
flex-shrink: 0;
max-width: 5rem;
max-height: 5rem;
}
.quote-text {
-webkit-box-ordinal-group: 2;
order: 1;
padding-right: 1rem;
}
}
}
&.is-vertical-photo {
.bubble__container {
width: fit-content;
}
}
.reply {
max-width: 300px;
margin-bottom: 6px;
@ -908,7 +940,7 @@ @@ -908,7 +940,7 @@
}
&:not(.sticker):not(.emoji-big) {
&.hide-name, &:not(.is-group-first) {
&.hide-name, &:not(.is-group-first), &.is-out {
.reply {
margin-top: 6px;
}

1
src/scss/partials/_ckin.scss

@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
video {
max-height: none;
max-width: none;
object-fit: contain;
}
}

48
src/scss/partials/_mediaViewer.scss

@ -209,4 +209,52 @@ $move-duration: .35s; @@ -209,4 +209,52 @@ $move-duration: .35s;
&-mover.active &-aspecter {
transition: $open-duration all;
}
&-whole {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: fixed!important;
display: block;
z-index: 4;
visibility: hidden;
transition: visibility 0s $open-duration;
&.active {
visibility: visible;
transition-delay: 0s;
.overlays {
opacity: 1;
visibility: visible;
-webkit-transition: opacity $open-duration 0s, visibility 0s 0s;
-moz-transition: opacity $open-duration 0s, visibility 0s 0s;
transition: opacity $open-duration 0s, visibility 0s 0s;
}
}
}
&-switchers {
position: relative;
width: $large-screen;
height: 100%;
margin: 0 auto;
}
}
.overlays {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: fixed!important;
background-color: rgba(0, 0, 0, .2);
z-index: 4;
//display: none;
opacity: 0;
visibility: hidden;
-webkit-transition: opacity $open-duration 0s, visibility 0s $open-duration;
-moz-transition: opacity $open-duration 0s, visibility 0s $open-duration;
transition: opacity $open-duration 0s, visibility 0s $open-duration;
}

24
src/scss/style.scss

@ -1336,30 +1336,6 @@ img.emoji { @@ -1336,30 +1336,6 @@ img.emoji {
}
}
.overlays {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: fixed!important;
background-color: rgba(0, 0, 0, .2);
z-index: 4;
//display: none;
opacity: 0;
visibility: hidden;
-webkit-transition: opacity 0.2s 0s, visibility 0s 0.2s;
-moz-transition: opacity 0.2s 0s, visibility 0s 0.2s;
transition: opacity 0.2s 0s, visibility 0s 0.2s;
&.active {
opacity: 1;
visibility: visible;
-webkit-transition: opacity 0.2s 0s, visibility 0s 0s;
-moz-transition: opacity 0.2s 0s, visibility 0s 0s;
transition: opacity 0.2s 0s, visibility 0s 0s;
}
}
[contenteditable] {
-webkit-user-select: text;
user-select: text;

8
webpack.common.js

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111', '192.168.0.105'];
let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '192.168.0.111', '192.168.0.105', '192.168.0.108'];
const opts = {
MTPROTO_WORKER: true,
@ -66,8 +66,8 @@ module.exports = { @@ -66,8 +66,8 @@ module.exports = {
//entry: './src/index.ts',
entry: {
index: './src/index.ts',
webp: './src/lib/webp.ts',
lottie: './src/lib/lottie.ts'
webp: './src/lib/webp.ts'/* ,
lottie: './src/lib/lottie.ts' */
},
/* entry: {
index: './src/index.ts',
@ -129,7 +129,7 @@ module.exports = { @@ -129,7 +129,7 @@ module.exports = {
minifyURLs: true
}, */
chunks: "all",
excludeChunks: ['npm.webp-hero', 'npm.lottie-web']
excludeChunks: ['npm.webp-hero'/* , 'npm.lottie-web' */]
})
],
};

8
webpack.prod.js

@ -59,7 +59,13 @@ module.exports = merge(common, { @@ -59,7 +59,13 @@ module.exports = merge(common, {
files.forEach(file => {
//console.log('to unlink 1:', file);
if(file.includes('mitm.') || file.includes('sw.js') || file.includes('.xml') || file.includes('.webmanifest')) return;
if(file.includes('mitm.')
|| file.includes('sw.js')
|| file.includes('.xml')
|| file.includes('.webmanifest')
|| file.includes('.wasm')
|| file.includes('rlottie')
|| file.includes('pako')) return;
let p = path.resolve(buildDir + file);
if(!newlyCreatedAssets[file] && ['.gz', '.js'].find(ext => file.endsWith(ext)) !== undefined) {

Loading…
Cancel
Save