Browse Source

Premium stickers

Faster file downloading
master
Eduard Kuzmenko 2 years ago
parent
commit
b9e6151d5c
  1. 41
      src/components/animationIntersector.ts
  2. 14
      src/components/appSearch.ts
  3. 3
      src/components/appSearchSuper..ts
  4. 37
      src/components/chat/bubbles.ts
  5. 3
      src/components/chat/inlineHelper.ts
  6. 29
      src/components/chat/input.ts
  7. 6
      src/components/chat/reactionsMenu.ts
  8. 4
      src/components/emoticonsDropdown/index.ts
  9. 76
      src/components/emoticonsDropdown/tabs/stickers.ts
  10. 4
      src/components/gifsMasonry.ts
  11. 7
      src/components/popups/stickers.ts
  12. 5
      src/components/scrollable.ts
  13. 2
      src/components/sidebarLeft/index.ts
  14. 4
      src/components/sidebarRight/tabs/gifs.ts
  15. 3
      src/components/sidebarRight/tabs/stickers.ts
  16. 143
      src/components/wrappers/sticker.ts
  17. 26
      src/components/wrappers/stickerAnimation.ts
  18. 4
      src/components/wrappers/stickerSetThumb.ts
  19. 4
      src/components/wrappers/video.ts
  20. 3
      src/config/app.ts
  21. 46
      src/helpers/averageColor.ts
  22. 31
      src/helpers/computeLockColor.ts
  23. 2
      src/helpers/fileName.ts
  24. 5
      src/helpers/overlayClickHandler.ts
  25. 9
      src/helpers/setWorkerProxy.ts
  26. 1
      src/lang.ts
  27. 10
      src/lib/appManagers/appDialogsManager.ts
  28. 6
      src/lib/appManagers/appImManager.ts
  29. 28
      src/lib/appManagers/appManagersManager.ts
  30. 10
      src/lib/appManagers/appMessagesManager.ts
  31. 12
      src/lib/appManagers/appStickersManager.ts
  32. 15
      src/lib/crypto/crypto.worker.ts
  33. 21
      src/lib/crypto/cryptoMessagePort.ts
  34. 21
      src/lib/mtproto/apiFileManager.ts
  35. 1
      src/lib/mtproto/mtproto.worker.ts
  36. 1
      src/lib/mtproto/mtprotoMessagePort.ts
  37. 72
      src/lib/mtproto/mtprotoworker.ts
  38. 13
      src/lib/mtproto/networker.ts
  39. 6
      src/lib/mtproto/transports/http.ts
  40. 9
      src/lib/mtproto/transports/obfuscation.ts
  41. 6
      src/lib/mtproto/transports/tcpObfuscated.ts
  42. 8
      src/lib/rlottie/lottieLoader.ts
  43. 10
      src/lib/rlottie/rlottiePlayer.ts
  44. 8
      src/scss/mixins/_premium.scss
  45. 40
      src/scss/style.scss

41
src/components/animationIntersector.ts

@ -14,9 +14,12 @@ import forEachReverse from '../helpers/array/forEachReverse'; @@ -14,9 +14,12 @@ import forEachReverse from '../helpers/array/forEachReverse';
import idleController from '../helpers/idleController';
import appMediaPlaybackController from './appMediaPlaybackController';
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS';
export interface AnimationItem {
el: HTMLElement,
group: string,
group: AnimationItemGroup,
animation: RLottiePlayer | HTMLVideoElement
};
@ -25,11 +28,11 @@ export class AnimationIntersector { @@ -25,11 +28,11 @@ export class AnimationIntersector {
private visible: Set<AnimationItem> = new Set();
private overrideIdleGroups: Set<string>;
private byGroups: {[group: string]: AnimationItem[]} = {};
private lockedGroups: {[group: string]: true} = {};
private onlyOnePlayableGroup: string = '';
private byGroups: {[group in AnimationItemGroup]?: AnimationItem[]} = {};
private lockedGroups: {[group in AnimationItemGroup]?: true} = {};
private onlyOnePlayableGroup: AnimationItemGroup = '';
private intersectionLockedGroups: {[group: string]: true} = {};
private intersectionLockedGroups: {[group in AnimationItemGroup]?: true} = {};
private videosLocked = false;
constructor() {
@ -40,11 +43,11 @@ export class AnimationIntersector { @@ -40,11 +43,11 @@ export class AnimationIntersector {
const target = entry.target;
for(const group in this.byGroups) {
if(this.intersectionLockedGroups[group]) {
if(this.intersectionLockedGroups[group as AnimationItemGroup]) {
continue;
}
const player = this.byGroups[group].find((p) => p.el === target);
const player = this.byGroups[group as AnimationItemGroup].find((p) => p.el === target);
if(player) {
if(entry.isIntersecting) {
this.visible.add(player);
@ -104,7 +107,7 @@ export class AnimationIntersector { @@ -104,7 +107,7 @@ export class AnimationIntersector {
public getAnimations(element: HTMLElement) {
const found: AnimationItem[] = [];
for(const group in this.byGroups) {
for(const player of this.byGroups[group]) {
for(const player of this.byGroups[group as AnimationItemGroup]) {
if(player.el === element) {
found.push(player);
}
@ -138,8 +141,8 @@ export class AnimationIntersector { @@ -138,8 +141,8 @@ export class AnimationIntersector {
this.visible.delete(player);
}
public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group = '') {
const player = {
public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group: AnimationItemGroup = '') {
const player: AnimationItem = {
el: animation instanceof RLottiePlayer ? animation.el : animation,
animation: animation,
group
@ -151,11 +154,11 @@ export class AnimationIntersector { @@ -151,11 +154,11 @@ export class AnimationIntersector {
}
}
(this.byGroups[group] ?? (this.byGroups[group] = [])).push(player);
(this.byGroups[group as AnimationItemGroup] ??= []).push(player);
this.observer.observe(player.el);
}
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
// if(rootScope.idle.isIDLE) return;
if(group !== undefined && !this.byGroups[group]) {
@ -163,7 +166,7 @@ export class AnimationIntersector { @@ -163,7 +166,7 @@ export class AnimationIntersector {
return;
}
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups);
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups) as AnimationItemGroup[];
for(const group of groups) {
const animations = this.byGroups[group];
@ -198,20 +201,20 @@ export class AnimationIntersector { @@ -198,20 +201,20 @@ export class AnimationIntersector {
}
}
public setOnlyOnePlayableGroup(group: string) {
public setOnlyOnePlayableGroup(group: AnimationItemGroup) {
this.onlyOnePlayableGroup = group;
}
public lockGroup(group: string) {
public lockGroup(group: AnimationItemGroup) {
this.lockedGroups[group] = true;
}
public unlockGroup(group: string) {
public unlockGroup(group: AnimationItemGroup) {
delete this.lockedGroups[group];
this.checkAnimations(undefined, group);
}
public refreshGroup(group: string) {
public refreshGroup(group: AnimationItemGroup) {
const animations = this.byGroups[group];
if(animations && animations.length) {
animations.forEach((animation) => {
@ -226,11 +229,11 @@ export class AnimationIntersector { @@ -226,11 +229,11 @@ export class AnimationIntersector {
}
}
public lockIntersectionGroup(group: string) {
public lockIntersectionGroup(group: AnimationItemGroup) {
this.intersectionLockedGroups[group] = true;
}
public unlockIntersectionGroup(group: string) {
public unlockIntersectionGroup(group: AnimationItemGroup) {
delete this.intersectionLockedGroups[group];
this.refreshGroup(group);
}

14
src/components/appSearch.ts

@ -23,7 +23,8 @@ export class SearchGroup { @@ -23,7 +23,8 @@ export class SearchGroup {
className?: string,
clickable = true,
public autonomous = true,
public onFound?: () => void
public onFound?: () => void,
public noIcons?: boolean
) {
this.list = appDialogsManager.createChatList();
this.container = document.createElement('div');
@ -87,7 +88,13 @@ export default class AppSearch { @@ -87,7 +88,13 @@ export default class AppSearch {
private scrollable: Scrollable;
constructor(public container: HTMLElement, public searchInput: InputSearch, public searchGroups: {[group in SearchGroupType]: SearchGroup}, public onSearch?: (count: number) => void) {
constructor(
public container: HTMLElement,
public searchInput: InputSearch,
public searchGroups: {[group in SearchGroupType]: SearchGroup},
public onSearch?: (count: number) => void,
public noIcons?: boolean
) {
this.scrollable = new Scrollable(this.container);
this.listsContainer = this.scrollable.container as HTMLDivElement;
for(const i in this.searchGroups) {
@ -200,7 +207,8 @@ export default class AppSearch { @@ -200,7 +207,8 @@ export default class AppSearch {
avatarSize: 54,
meAsSaved: false,
message,
query
query,
noIcons: this.noIcons
});
} catch(err) {
console.error('[appSearch] render search result', err);

3
src/components/appSearchSuper..ts

@ -1116,7 +1116,8 @@ export default class AppSearchSuper { @@ -1116,7 +1116,8 @@ export default class AppSearchSuper {
container: this.searchGroups.people.list,
onlyFirstName: true,
avatarSize: 54,
autonomous: false
autonomous: false,
noIcons: this.searchGroups.people.noIcons
});
});
}

37
src/components/chat/bubbles.ts

@ -110,6 +110,7 @@ import noop from '../../helpers/noop'; @@ -110,6 +110,7 @@ import noop from '../../helpers/noop';
import getAlbumText from '../../lib/appManagers/utils/messages/getAlbumText';
import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount';
import PopupPayment from '../popups/payment';
import isInDOM from '../../helpers/dom/isInDOM';
const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
@ -1047,6 +1048,19 @@ export default class ChatBubbles { @@ -1047,6 +1048,19 @@ export default class ChatBubbles {
}
};
private stickerEffectObserverCallback = (entry: IntersectionObserverEntry) => {
if(entry.isIntersecting) {
this.observer.unobserve(entry.target, this.stickerEffectObserverCallback);
const attachmentDiv: HTMLElement = entry.target.querySelector('.attachment');
getHeavyAnimationPromise().then(() => {
if(isInDOM(attachmentDiv)) {
simulateClickEvent(attachmentDiv);
}
});
}
};
private createResizeObserver() {
if(!('ResizeObserver' in window) || this.resizeObserver) {
return;
@ -2110,6 +2124,8 @@ export default class ChatBubbles { @@ -2110,6 +2124,8 @@ export default class ChatBubbles {
this.observer.unobserve(bubble, this.viewsObserverCallback);
this.viewsMids.delete(mid);
this.observer.unobserve(bubble, this.stickerEffectObserverCallback);
}
if(this.emptyPlaceholderBubble === bubble) {
@ -3426,15 +3442,13 @@ export default class ChatBubbles { @@ -3426,15 +3442,13 @@ export default class ChatBubbles {
contentWrapper.append(bubbleContainer);
bubble.append(contentWrapper);
if(!our && !message.pFlags.out && this.observer) {
const isInUnread = !our && !message.pFlags.out && (message.pFlags.unread ||
isMentionUnread(message)/* ||
(this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid) */);
if(isInUnread && this.observer) {
// this.log('not our message', message, message.pFlags.unread);
const isUnread = message.pFlags.unread ||
isMentionUnread(message)/* ||
(this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid) */;
if(isUnread) {
this.observer.observe(bubble, this.unreadedObserverCallback);
this.unreaded.set(bubble, message.mid);
}
this.observer.observe(bubble, this.unreadedObserverCallback);
this.unreaded.set(bubble, message.mid);
}
const loadPromises: Promise<any>[] = [];
@ -4017,8 +4031,13 @@ export default class ChatBubbles { @@ -4017,8 +4031,13 @@ export default class ChatBubbles {
emoji: bubble.classList.contains('emoji-big') ? messageMessage : undefined,
withThumb: true,
loadPromises,
isOut
isOut,
noPremium: messageMedia?.pFlags?.nopremium
});
if(isInUnread || isOutgoing/* || true */) {
this.observer.observe(bubble, this.stickerEffectObserverCallback);
}
} else if(doc.type === 'video' || doc.type === 'gif' || doc.type === 'round'/* && doc.size <= 20e6 */) {
// this.log('never get free 2', doc);

3
src/components/chat/inlineHelper.ts

@ -28,8 +28,9 @@ import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; @@ -28,8 +28,9 @@ import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
import wrapRichText from '../../lib/richTextProcessor/wrapRichText';
import generateQId from '../../lib/appManagers/utils/inlineBots/generateQId';
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import {AnimationItemGroup} from '../animationIntersector';
const ANIMATION_GROUP = 'INLINE-HELPER';
const ANIMATION_GROUP: AnimationItemGroup = 'INLINE-HELPER';
// const GRID_ITEMS = 5;
export default class InlineHelper extends AutocompleteHelper {

29
src/components/chat/input.ts

@ -94,6 +94,7 @@ import {modifyAckedPromise} from '../../helpers/modifyAckedResult'; @@ -94,6 +94,7 @@ import {modifyAckedPromise} from '../../helpers/modifyAckedResult';
import ChatSendAs from './sendAs';
import filterAsync from '../../helpers/array/filterAsync';
import InputFieldAnimated from '../inputFieldAnimated';
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -2448,22 +2449,26 @@ export default class ChatInput { @@ -2448,22 +2449,26 @@ export default class ChatInput {
return false;
}
if(document) {
this.managers.appMessagesManager.sendFile(this.chat.peerId, document, {
...this.chat.getMessageSendingParams(),
isMedia: true,
clearDraft: clearDraft || undefined
});
this.onMessageSent(clearDraft, true);
if(!document) {
return false;
}
if(document.type === 'sticker') {
emoticonsDropdown.stickersTab?.pushRecentSticker(document);
}
if(getStickerEffectThumb(document) && !rootScope.premium) {
return false;
}
this.managers.appMessagesManager.sendFile(this.chat.peerId, document, {
...this.chat.getMessageSendingParams(),
isMedia: true,
clearDraft: clearDraft || undefined
});
this.onMessageSent(clearDraft, true);
return true;
if(document.type === 'sticker') {
emoticonsDropdown.stickersTab?.pushRecentSticker(document);
}
return false;
return true;
}
private canToggleHideAuthor() {

6
src/components/chat/reactionsMenu.ts

@ -19,7 +19,7 @@ import {AppManagers} from '../../lib/appManagers/managers'; @@ -19,7 +19,7 @@ import {AppManagers} from '../../lib/appManagers/managers';
import lottieLoader from '../../lib/rlottie/lottieLoader';
import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
import rootScope from '../../lib/rootScope';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import Scrollable, {ScrollableBase, ScrollableX} from '../scrollable';
import {wrapSticker} from '../wrappers';
@ -44,7 +44,7 @@ export class ChatReactionsMenu { @@ -44,7 +44,7 @@ export class ChatReactionsMenu {
public container: HTMLElement;
private reactionsMap: Map<HTMLElement, ChatReactionsMenuPlayers>;
public scrollable: ScrollableBase;
private animationGroup: string;
private animationGroup: AnimationItemGroup;
private middleware: ReturnType<typeof getMiddleware>;
private message: Message.message;
@ -74,7 +74,7 @@ export class ChatReactionsMenu { @@ -74,7 +74,7 @@ export class ChatReactionsMenu {
// });
this.reactionsMap = new Map();
this.animationGroup = 'CHAT-MENU-REACTIONS-' + Date.now();
this.animationGroup = `CHAT-MENU-REACTIONS-${Date.now()}`;
animationIntersector.setOverrideIdleGroup(this.animationGroup, true);
if(!IS_TOUCH_SUPPORTED) {

4
src/components/emoticonsDropdown/index.ts

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
import appImManager from '../../lib/appManagers/appImManager';
import rootScope from '../../lib/rootScope';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import {horizontalMenu} from '../horizontalMenu';
import LazyLoadQueue from '../lazyLoadQueue';
import Scrollable, {ScrollableX} from '../scrollable';
@ -31,7 +31,7 @@ import {AppManagers} from '../../lib/appManagers/managers'; @@ -31,7 +31,7 @@ import {AppManagers} from '../../lib/appManagers/managers';
import type LazyLoadQueueIntersector from '../lazyLoadQueueIntersector';
import {simulateClickEvent} from '../../helpers/dom/clickEvent';
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
export const EMOTICONSSTICKERGROUP: AnimationItemGroup = 'emoticons-dropdown';
export interface EmoticonsTab {
init: () => void,

76
src/components/emoticonsDropdown/tabs/stickers.ts

@ -5,16 +5,15 @@ @@ -5,16 +5,15 @@
*/
import emoticonsDropdown, {EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab} from '..';
import findUpAttribute from '../../../helpers/dom/findUpAttribute';
import findUpClassName from '../../../helpers/dom/findUpClassName';
import mediaSizes from '../../../helpers/mediaSizes';
import {MessagesAllStickers, StickerSet} from '../../../layer';
import {MyDocument} from '../../../lib/appManagers/appDocsManager';
import {AppManagers} from '../../../lib/appManagers/managers';
import {i18n} from '../../../lib/langPack';
import {i18n, LangPackKey} from '../../../lib/langPack';
import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import rootScope from '../../../lib/rootScope';
import animationIntersector from '../../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../../animationIntersector';
import LazyLoadQueue from '../../lazyLoadQueue';
import LazyLoadQueueRepeat from '../../lazyLoadQueueRepeat';
import {putPreloader} from '../../putPreloader';
@ -36,7 +35,7 @@ export class SuperStickerRenderer { @@ -36,7 +35,7 @@ export class SuperStickerRenderer {
constructor(
private regularLazyLoadQueue: LazyLoadQueue,
private group: string,
private group: AnimationItemGroup,
private managers: AppManagers,
private options?: IntersectionObserverInit
) {
@ -117,7 +116,8 @@ export class SuperStickerRenderer { @@ -117,7 +116,8 @@ export class SuperStickerRenderer {
group: this.group,
onlyThumb: false,
play: true,
loop: true
loop: true,
withLock: true
}).then(({render}) => render);
promise.then(() => {
@ -157,7 +157,8 @@ type StickersTabCategory = { @@ -157,7 +157,8 @@ type StickersTabCategory = {
items: {
document: MyDocument,
element: HTMLElement
}[]
}[],
pos?: number
};
const RECENT_STICKERS_COUNT = 20;
@ -168,6 +169,7 @@ export default class StickersTab implements EmoticonsTab { @@ -168,6 +169,7 @@ export default class StickersTab implements EmoticonsTab {
private categories: {[id: string]: StickersTabCategory};
private categoriesMap: Map<HTMLElement, StickersTabCategory>;
private categoriesIntersector: VisibilityIntersector;
private localCategoryIndex: number;
private scroll: Scrollable;
private menu: HTMLElement;
@ -178,6 +180,7 @@ export default class StickersTab implements EmoticonsTab { @@ -178,6 +180,7 @@ export default class StickersTab implements EmoticonsTab {
constructor(private managers: AppManagers) {
this.categories = {};
this.categoriesMap = new Map();
this.localCategoryIndex = 0;
}
private createCategory(stickerSet: StickerSet.stickerSet, _title: HTMLElement | DocumentFragment) {
@ -264,7 +267,8 @@ export default class StickersTab implements EmoticonsTab { @@ -264,7 +267,8 @@ export default class StickersTab implements EmoticonsTab {
const category = this.createCategory(set, wrapEmojiText(set.title));
const {menuTab, menuTabPadding, container} = category.elements;
positionElementByIndex(menuTab, this.menu, prepend ? 1 : 0xFFFF);
const pos = prepend ? this.localCategoryIndex : 0xFFFF;
positionElementByIndex(menuTab, this.menu, pos);
const promise = this.managers.appStickersManager.getStickerSet(set);
this.categoryAppendStickers(
@ -273,7 +277,7 @@ export default class StickersTab implements EmoticonsTab { @@ -273,7 +277,7 @@ export default class StickersTab implements EmoticonsTab {
);
// const stickerSet = await promise;
positionElementByIndex(container, this.scroll.container, prepend ? 1 : 0xFFFF, -1);
positionElementByIndex(container, this.scroll.container, pos, -1);
wrapStickerSetThumb({
set,
@ -367,11 +371,18 @@ export default class StickersTab implements EmoticonsTab { @@ -367,11 +371,18 @@ export default class StickersTab implements EmoticonsTab {
const preloader = putPreloader(this.content, true);
const recentCategory = this.createCategory({id: 'recent'} as any, i18n('Stickers.Recent'));
recentCategory.elements.title.classList.add('disable-hover');
recentCategory.elements.menuTab.classList.add('tgico-recent', 'active');
recentCategory.elements.menuTabPadding.remove();
this.toggleRecentCategory(recentCategory, false);
const createLocalCategory = (id: string, title: LangPackKey, icon?: string) => {
const category = this.createCategory({id} as any, i18n(title));
category.elements.title.classList.add('disable-hover');
icon && category.elements.menuTab.classList.add('tgico-' + icon);
category.elements.menuTabPadding.remove();
category.pos = this.localCategoryIndex++;
this.toggleLocalCategory(category, false);
return category;
};
const recentCategory = createLocalCategory('recent', 'Stickers.Recent', 'recent');
recentCategory.elements.menuTab.classList.add('active');
const clearButton = ButtonIcon('close', {noRipple: true});
recentCategory.elements.title.append(clearButton);
@ -391,24 +402,42 @@ export default class StickersTab implements EmoticonsTab { @@ -391,24 +402,42 @@ export default class StickersTab implements EmoticonsTab {
const sliced = stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[];
clearCategoryItems(recentCategory);
this.toggleRecentCategory(recentCategory, !!sliced.length);
this.toggleLocalCategory(recentCategory, !!sliced.length);
this.categoryAppendStickers(recentCategory, Promise.resolve(sliced));
};
Promise.all([
const premiumCategory = createLocalCategory('premium', 'PremiumStickersShort');
const s = document.createElement('span');
s.classList.add('tgico-star', 'color-premium');
premiumCategory.elements.menuTab.append(s);
const promises = [
this.managers.appStickersManager.getRecentStickers().then((stickers) => {
preloader.remove();
onRecentStickers(stickers.stickers as MyDocument[]);
}),
this.managers.appStickersManager.getAllStickers().then((res) => {
preloader.remove();
for(const set of (res as MessagesAllStickers.messagesAllStickers).sets) {
this.renderStickerSet(set);
}
}),
this.managers.appStickersManager.getPremiumStickers().then((stickers) => {
const length = stickers.length;
this.toggleLocalCategory(premiumCategory, rootScope.premium && !!length);
this.categoryAppendStickers(premiumCategory, Promise.resolve(stickers));
rootScope.addEventListener('premium_toggle', (isPremium) => {
this.toggleLocalCategory(this.categories['premium'], isPremium && !!length);
});
})
]).finally(() => {
];
Promise.race(promises).finally(() => {
preloader.remove();
});
Promise.all(promises).finally(() => {
this.mounted = true;
setTyping();
setActive(0);
@ -482,13 +511,14 @@ export default class StickersTab implements EmoticonsTab { @@ -482,13 +511,14 @@ export default class StickersTab implements EmoticonsTab {
this.init = null;
}
private toggleRecentCategory(category: StickersTabCategory, visible: boolean) {
private toggleLocalCategory(category: StickersTabCategory, visible: boolean) {
if(!visible) {
category.elements.menuTab.remove();
category.elements.container.remove();
} else {
positionElementByIndex(category.elements.menuTab, this.menu, 0);
positionElementByIndex(category.elements.container, this.scroll.container, 0);
const pos = category.pos;
positionElementByIndex(category.elements.menuTab, this.menu, pos);
positionElementByIndex(category.elements.container, this.scroll.container, pos);
}
// category.elements.container.classList.toggle('hide', !visible);
@ -518,7 +548,7 @@ export default class StickersTab implements EmoticonsTab { @@ -518,7 +548,7 @@ export default class StickersTab implements EmoticonsTab {
}
this.setCategoryItemsHeight(category);
this.toggleRecentCategory(category, true);
this.toggleLocalCategory(category, true);
}
onClose() {

4
src/components/gifsMasonry.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import type {MyDocument} from '../lib/appManagers/appDocsManager';
import {wrapVideo} from './wrappers';
import animationIntersector from './animationIntersector';
import animationIntersector, {AnimationItemGroup} from './animationIntersector';
import Scrollable from './scrollable';
import deferredPromise, {CancellablePromise} from '../helpers/cancellablePromise';
import renderImageFromUrl from '../helpers/dom/renderImageFromUrl';
@ -28,7 +28,7 @@ export default class GifsMasonry { @@ -28,7 +28,7 @@ export default class GifsMasonry {
constructor(
private element: HTMLElement,
private group: string,
private group: AnimationItemGroup,
private scrollable: Scrollable,
attach = true
) {

7
src/components/popups/stickers.ts

@ -9,7 +9,7 @@ import type {AppStickersManager} from '../../lib/appManagers/appStickersManager' @@ -9,7 +9,7 @@ import type {AppStickersManager} from '../../lib/appManagers/appStickersManager'
import {wrapSticker} from '../wrappers';
import LazyLoadQueue from '../lazyLoadQueue';
import {putPreloader} from '../putPreloader';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import appImManager from '../../lib/appManagers/appImManager';
import mediaSizes from '../../helpers/mediaSizes';
import {i18n} from '../../lib/langPack';
@ -21,7 +21,7 @@ import {toastNew} from '../toast'; @@ -21,7 +21,7 @@ import {toastNew} from '../toast';
import setInnerHTML from '../../helpers/dom/setInnerHTML';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
const ANIMATION_GROUP = 'STICKERS-POPUP';
const ANIMATION_GROUP: AnimationItemGroup = 'STICKERS-POPUP';
export default class PopupStickers extends PopupElement {
private stickersFooter: HTMLElement;
@ -126,7 +126,8 @@ export default class PopupStickers extends PopupElement { @@ -126,7 +126,8 @@ export default class PopupStickers extends PopupElement {
play: true,
loop: true,
width: size,
height: size
height: size,
withLock: true
});
return div;

5
src/components/scrollable.ts

@ -9,6 +9,7 @@ import {logger, LogTypes} from '../lib/logger'; @@ -9,6 +9,7 @@ import {logger, LogTypes} from '../lib/logger';
import fastSmoothScroll, {ScrollOptions} from '../helpers/fastSmoothScroll';
import useHeavyAnimationCheck from '../hooks/useHeavyAnimationCheck';
import cancelEvent from '../helpers/dom/cancelEvent';
import {IS_ANDROID} from '../environment/userAgent';
/*
var el = $0;
var height = 0;
@ -52,6 +53,8 @@ const scrollsIntersector = new IntersectionObserver((entries) => { @@ -52,6 +53,8 @@ const scrollsIntersector = new IntersectionObserver((entries) => {
}
}); */
const SCROLL_THROTTLE = IS_ANDROID ? 200 : 24;
export class ScrollableBase {
protected log: ReturnType<typeof logger>;
@ -196,7 +199,7 @@ export class ScrollableBase { @@ -196,7 +199,7 @@ export class ScrollableBase {
this.checkForTriggers();
}
// });
}, 200);
}, SCROLL_THROTTLE);
};
public cancelMeasure() {

2
src/components/sidebarLeft/index.ts

@ -367,7 +367,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -367,7 +367,7 @@ export class AppSidebarLeft extends SidebarSlider {
contacts: new SearchGroup('SearchAllChatsShort', 'contacts', undefined, undefined, undefined, undefined, close),
globalContacts: new SearchGroup('GlobalSearch', 'contacts', undefined, undefined, undefined, undefined, close),
messages: new SearchGroup('SearchMessages', 'messages'),
people: new SearchGroup(false, 'contacts', true, 'search-group-people', true, false, close),
people: new SearchGroup(false, 'contacts', true, 'search-group-people', true, false, close, true),
recent: new SearchGroup('Recent', 'contacts', true, 'search-group-recent', true, true, close)
};

4
src/components/sidebarRight/tabs/gifs.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import {SliderSuperTab} from '../../slider';
import InputSearch from '../../inputSearch';
import animationIntersector from '../../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../../animationIntersector';
import appSidebarRight from '..';
import {AppInlineBotsManager} from '../../../lib/appManagers/appInlineBotsManager';
import GifsMasonry from '../../gifsMasonry';
@ -17,7 +17,7 @@ import findUpClassName from '../../../helpers/dom/findUpClassName'; @@ -17,7 +17,7 @@ import findUpClassName from '../../../helpers/dom/findUpClassName';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import {NULL_PEER_ID} from '../../../lib/mtproto/mtproto_config';
const ANIMATIONGROUP = 'GIFS-SEARCH';
const ANIMATIONGROUP: AnimationItemGroup = 'GIFS-SEARCH';
export default class AppGifsTab extends SliderSuperTab {
private inputSearch: InputSearch;

3
src/components/sidebarRight/tabs/stickers.ts

@ -151,7 +151,8 @@ export default class AppStickersTab extends SliderSuperTab { @@ -151,7 +151,8 @@ export default class AppStickersTab extends SliderSuperTab {
play: true,
loop: true,
width: 68,
height: 68
height: 68,
withLock: true
});
}
});

143
src/components/wrappers/sticker.ts

@ -8,6 +8,7 @@ import IS_WEBP_SUPPORTED from '../../environment/webpSupport'; @@ -8,6 +8,7 @@ import IS_WEBP_SUPPORTED from '../../environment/webpSupport';
import assumeType from '../../helpers/assumeType';
import getPathFromBytes from '../../helpers/bytes/getPathFromBytes';
import deferredPromise from '../../helpers/cancellablePromise';
import computeLockColor from '../../helpers/computeLockColor';
import cancelEvent from '../../helpers/dom/cancelEvent';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import createVideo from '../../helpers/dom/createVideo';
@ -34,7 +35,7 @@ import type {ThumbCache} from '../../lib/storages/thumbs'; @@ -34,7 +35,7 @@ import type {ThumbCache} from '../../lib/storages/thumbs';
import webpWorkerController from '../../lib/webp/webpWorkerController';
import {SendMessageEmojiInteractionData} from '../../types';
import {getEmojiToneIndex} from '../../vendor/emoji';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import LazyLoadQueue from '../lazyLoadQueue';
import PopupStickers from '../popups/stickers';
import {hideToast, toastNew} from '../toast';
@ -44,12 +45,14 @@ import wrapStickerAnimation from './stickerAnimation'; @@ -44,12 +45,14 @@ import wrapStickerAnimation from './stickerAnimation';
const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
const EMOJI_EFFECT_MULTIPLIER = 3;
export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut}: {
const locksUrls: {[docId: string]: string} = {};
export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut, noPremium, withLock}: {
doc: MyDocument,
div: HTMLElement,
middleware?: () => boolean,
lazyLoadQueue?: LazyLoadQueue,
group?: string,
group?: AnimationItemGroup,
play?: boolean,
onlyThumb?: boolean,
emoji?: string,
@ -64,7 +67,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -64,7 +67,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
static?: boolean,
managers?: AppManagers,
fullThumb?: PhotoSize | VideoSize,
isOut?: boolean
isOut?: boolean,
noPremium?: boolean,
withLock?: boolean
}) {
const stickerType = doc.sticker;
if(stickerType === 1) {
@ -134,6 +139,13 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -134,6 +139,13 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
div.classList.add('reflect-x');
}
const willHaveLock = effectThumb && withLock;
if(willHaveLock) {
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
const lockUrl = locksUrls[doc.id];
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
}
if(asStatic && stickerType !== 1) {
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
await getCacheContext(thumb.type);
@ -331,6 +343,11 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -331,6 +343,11 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
// const deferred = deferredPromise<void>();
const setLockColor = willHaveLock ? () => {
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas);
div.style.setProperty('--lock-url', `url(${lockUrl})`);
} : undefined;
animation.addEventListener('firstFrame', () => {
const element = div.firstElementChild;
if(needFadeIn !== false) {
@ -367,6 +384,10 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -367,6 +384,10 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
saveLottiePreview(doc, animation.canvas, toneIndex);
}
if(willHaveLock) {
setLockColor();
}
// deferred.resolve();
}, {once: true});
@ -473,54 +494,6 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -473,54 +494,6 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
sendInteractionThrottled();
}
});
} else if(effectThumb && isOut !== undefined) {
managers.appStickersManager.preloadSticker(doc.id, true);
let playing = false;
attachClickEvent(div, async(e) => {
cancelEvent(e);
if(playing) {
const a = document.createElement('a');
a.onclick = () => {
hideToast();
new PopupStickers(doc.stickerSetInput).show();
};
toastNew({
langPackKey: 'Sticker.Premium.Click.Info',
langPackArguments: [a]
});
return;
}
playing = true;
const {animationDiv, stickerPromise} = wrapStickerAnimation({
doc,
middleware,
side: isOut ? 'right' : 'left',
size: width * STICKER_EFFECT_MULTIPLIER,
target: div,
play: true,
fullThumb: effectThumb
});
if(isOut !== undefined && !isOut) {
animationDiv.classList.add('reflect-x');
}
stickerPromise.then((player) => {
const onFrame = (frameNo: number) => {
if(frameNo === player.maxFrame) {
playing = false;
player.removeEventListener('enterFrame', onFrame);
}
};
player.addEventListener('enterFrame', onFrame);
});
});
}
return animation;
@ -633,5 +606,71 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -633,5 +606,71 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
}
}
if(stickerType === 2 && effectThumb && isOut !== undefined && !noPremium) {
attachStickerEffectHandler({
container: div,
doc,
managers,
middleware,
isOut,
width,
loadPromise
});
}
return {render: loadPromise};
}
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise}: {
container: HTMLElement,
doc: MyDocument,
managers: AppManagers,
middleware: () => boolean,
isOut: boolean,
width: number,
loadPromise: Promise<any>
}) {
managers.appStickersManager.preloadSticker(doc.id, true);
let playing = false;
attachClickEvent(container, async(e) => {
cancelEvent(e);
if(playing) {
const a = document.createElement('a');
a.onclick = () => {
hideToast();
new PopupStickers(doc.stickerSetInput).show();
};
toastNew({
langPackKey: 'Sticker.Premium.Click.Info',
langPackArguments: [a]
});
return;
}
playing = true;
await loadPromise;
const {animationDiv, stickerPromise} = wrapStickerAnimation({
doc,
middleware,
side: isOut ? 'right' : 'left',
size: width * STICKER_EFFECT_MULTIPLIER,
target: container,
play: true,
fullThumb: getStickerEffectThumb(doc)
});
if(isOut !== undefined && !isOut) {
animationDiv.classList.add('reflect-x');
}
stickerPromise.then((player) => {
player.addEventListener('destroy', () => {
playing = false;
});
});
});
}

26
src/components/wrappers/stickerAnimation.ts

@ -8,6 +8,7 @@ import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport'; @@ -8,6 +8,7 @@ import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport';
import assumeType from '../../helpers/assumeType';
import isInDOM from '../../helpers/dom/isInDOM';
import throttleWithRaf from '../../helpers/schedulers/throttleWithRaf';
import windowSize from '../../helpers/windowSize';
import {PhotoSize, VideoSize} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager';
import appImManager from '../../lib/appManagers/appImManager';
@ -45,6 +46,13 @@ export default function wrapStickerAnimation({ @@ -45,6 +46,13 @@ export default function wrapStickerAnimation({
animationDiv.style.width = size + 'px';
animationDiv.style.height = size + 'px';
let animation: RLottiePlayer;
const unmountAnimation = () => {
animation?.remove();
animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
};
const stickerPromise = wrapSticker({
div: animationDiv,
doc,
@ -59,13 +67,12 @@ export default function wrapStickerAnimation({ @@ -59,13 +67,12 @@ export default function wrapStickerAnimation({
skipRatio,
managers,
fullThumb
}).then(({render}) => render).then((animation) => {
assumeType<RLottiePlayer>(animation);
}).then(({render}) => render).then((_animation) => {
assumeType<RLottiePlayer>(_animation);
animation = _animation;
animation.addEventListener('enterFrame', (frameNo) => {
if(frameNo === animation.maxFrame) {
animation.remove();
animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
if(frameNo === animation.maxFrame || !isInDOM(target)) {
unmountAnimation();
}
});
@ -88,6 +95,7 @@ export default function wrapStickerAnimation({ @@ -88,6 +95,7 @@ export default function wrapStickerAnimation({
const stableOffsetX = /* size / 8 */16 * (side === 'right' ? 1 : -1);
const setPosition = () => {
if(!isInDOM(target)) {
unmountAnimation();
return;
}
@ -104,6 +112,12 @@ export default function wrapStickerAnimation({ @@ -104,6 +112,12 @@ export default function wrapStickerAnimation({
// const y = rect.bottom - size + size / 4;
const y = rect.top + ((rect.height - size) / 2) + (side === 'center' ? 0 : randomOffsetY);
// animationDiv.style.transform = `translate(${x}px, ${y}px)`;
if(y <= -size || y >= windowSize.height) {
unmountAnimation();
return;
}
animationDiv.style.top = y + 'px';
animationDiv.style.left = x + 'px';
};

4
src/components/wrappers/stickerSetThumb.ts

@ -11,7 +11,7 @@ import appDownloadManager from '../../lib/appManagers/appDownloadManager'; @@ -11,7 +11,7 @@ import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import {AppManagers} from '../../lib/appManagers/managers';
import lottieLoader from '../../lib/rlottie/lottieLoader';
import rootScope from '../../lib/rootScope';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import LazyLoadQueue from '../lazyLoadQueue';
import wrapSticker from './sticker';
@ -19,7 +19,7 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container @@ -19,7 +19,7 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
set: StickerSet.stickerSet,
lazyLoadQueue: LazyLoadQueue,
container: HTMLElement,
group: string,
group: AnimationItemGroup,
autoplay: boolean,
width: number,
height: number,

4
src/components/wrappers/video.ts

@ -27,7 +27,7 @@ import {AppManagers} from '../../lib/appManagers/managers'; @@ -27,7 +27,7 @@ import {AppManagers} from '../../lib/appManagers/managers';
import {NULL_PEER_ID} from '../../lib/mtproto/mtproto_config';
import rootScope from '../../lib/rootScope';
import {ThumbCache} from '../../lib/storages/thumbs';
import animationIntersector from '../animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
import appMediaPlaybackController, {MediaSearchContext} from '../appMediaPlaybackController';
import {findMediaTargets} from '../audio';
import LazyLoadQueue from '../lazyLoadQueue';
@ -71,7 +71,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH @@ -71,7 +71,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
lazyLoadQueue?: LazyLoadQueue,
noInfo?: boolean,
noPlayButton?: boolean,
group?: string,
group?: AnimationItemGroup,
onlyPreview?: boolean,
withoutPreloader?: boolean,
loadPromises?: Promise<any>[],

3
src/config/app.ts

@ -25,7 +25,8 @@ const App = { @@ -25,7 +25,8 @@ const App = {
domains: [MAIN_DOMAIN] as string[],
baseDcId: 2 as DcId,
isMainDomain: location.hostname === MAIN_DOMAIN,
suffix: 'K'
suffix: 'K',
cryptoWorkers: 4
};
if(App.isMainDomain) { // use Webogram credentials then

46
src/helpers/averageColor.ts

@ -11,42 +11,48 @@ export function averageColorFromCanvas(canvas: HTMLCanvasElement) { @@ -11,42 +11,48 @@ export function averageColorFromCanvas(canvas: HTMLCanvasElement) {
const pixel = new Array(4).fill(0);
const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
const pixelsLength = pixels.length / 4;
for(let i = 0; i < pixels.length; i += 4) {
pixel[0] += pixels[i];
pixel[1] += pixels[i + 1];
pixel[2] += pixels[i + 2];
// const alphaPixel = pixels[i + 3];
pixel[0] += pixels[i]/* * (alphaPixel / 255) */;
pixel[1] += pixels[i + 1]/* * (alphaPixel / 255) */;
pixel[2] += pixels[i + 2]/* * (alphaPixel / 255) */;
pixel[3] += pixels[i + 3];
}
const pixelsLength = pixels.length / 4;
const outPixel = new Uint8ClampedArray(4);
outPixel[0] = pixel[0] / pixelsLength;
outPixel[1] = pixel[1] / pixelsLength;
outPixel[2] = pixel[2] / pixelsLength;
outPixel[3] = pixel[3] / pixelsLength;
// outPixel[3] = 255;
return outPixel;
}
export function averageColorFromImageSource(imageSource: CanvasImageSource, width: number, height: number) {
const canvas = document.createElement('canvas');
const ratio = width / height;
const DIMENSIONS = 50;
if(ratio === 1) {
canvas.width = DIMENSIONS;
canvas.height = canvas.width / ratio;
} else if(ratio > 1) {
canvas.height = DIMENSIONS;
canvas.width = canvas.height / ratio;
} else {
canvas.width = canvas.height = DIMENSIONS;
}
const context = canvas.getContext('2d');
context.drawImage(imageSource, 0, 0, width, height, 0, 0, canvas.width, canvas.height);
return averageColorFromCanvas(canvas);
}
export function averageColor(imageUrl: string) {
const img = document.createElement('img');
return new Promise<Uint8ClampedArray>((resolve) => {
renderImageFromUrl(img, imageUrl, () => {
const canvas = document.createElement('canvas');
const ratio = img.naturalWidth / img.naturalHeight;
const DIMENSIONS = 50;
if(ratio === 1) {
canvas.width = DIMENSIONS;
canvas.height = canvas.width / ratio;
} else if(ratio > 1) {
canvas.height = DIMENSIONS;
canvas.width = canvas.height / ratio;
} else {
canvas.width = canvas.height = DIMENSIONS;
}
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, canvas.width, canvas.height);
resolve(averageColorFromCanvas(canvas));
resolve(averageColorFromImageSource(img, img.naturalWidth, img.naturalHeight));
});
});
};

31
src/helpers/computeLockColor.ts

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
// https://github.com/telegramdesktop/tdesktop/blob/543bfab24a76402992421063f1e6444f347d31fe/Telegram/SourceFiles/boxes/sticker_set_box.cpp#L75
export default function computeLockColor(canvas: HTMLCanvasElement) {
const context = canvas.getContext('2d');
const size = 20 * (canvas.dpr ?? 1);
const width = size;
const height = size;
const skipx = (canvas.width - width) / 2;
const margin = 0/* * (canvas.dpr ?? 1) */;
const skipy = canvas.height - height - margin;
const imageData = context.getImageData(skipx, skipy, width, height).data;
let sr = 0, sg = 0, sb = 0, sa = 0;
for(let i = 0; i < imageData.length; i += 4) {
sr += imageData[i];
sg += imageData[i + 1];
sb += imageData[i + 2];
sa += imageData[i + 3];
}
const outCanvas = document.createElement('canvas');
outCanvas.width = size;
outCanvas.height = size;
const outContext = outCanvas.getContext('2d');
const color = new Uint8ClampedArray([sr * 255 / sa, sg * 255 / sa, sb * 255 / sa, 255]);
const rgba = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
outContext.fillStyle = rgba;
outContext.fillRect(0, 0, outCanvas.width, outCanvas.height);
outContext.fillStyle = `rgba(112, 117, 121, 0.3)`;
outContext.fillRect(0, 0, outCanvas.width, outCanvas.height);
// document.querySelector('.popup-title').append(c);
return outCanvas.toDataURL('image/jpeg');
}

2
src/helpers/fileName.ts

@ -58,7 +58,7 @@ export function getFileNameByLocation(location: InputFileLocation | InputWebFile @@ -58,7 +58,7 @@ export function getFileNameByLocation(location: InputFileLocation | InputWebFile
}
}
return str + (options.downloadId ? '_download' : '') + (ext ? '.' + ext : ext);
return str + (options?.downloadId ? '_download' : '') + (ext ? '.' + ext : ext);
}
export type FileURLType = 'photo' | 'thumb' | 'document' | 'stream' | 'download';

5
src/helpers/overlayClickHandler.ts

@ -32,7 +32,10 @@ export default class OverlayClickHandler extends EventListenerBase<{ @@ -32,7 +32,10 @@ export default class OverlayClickHandler extends EventListenerBase<{
return;
}
cancelEvent(e);
if(this.listenerOptions?.capture) {
cancelEvent(e);
}
this.close();
};

9
src/helpers/setWorkerProxy.ts

@ -8,8 +8,10 @@ export default function setWorkerProxy() { @@ -8,8 +8,10 @@ export default function setWorkerProxy() {
// * hook worker constructor to set search parameters (test, debug, etc)
const workerHandler = {
construct(target: any, args: any) {
// console.log(target, args);
const url = args[0] + location.search;
let url = args[0] + '';
if(url.indexOf('blob:') !== 0) {
url += location.search;
}
return new target(url);
}
@ -18,8 +20,7 @@ export default function setWorkerProxy() { @@ -18,8 +20,7 @@ export default function setWorkerProxy() {
[
Worker,
typeof(SharedWorker) !== 'undefined' && SharedWorker
].forEach((w) => {
if(!w) return;
].filter(Boolean).forEach((w) => {
window[w.name as any] = new Proxy(w, workerHandler);
});
}

1
src/lang.ts

@ -748,6 +748,7 @@ const lang = { @@ -748,6 +748,7 @@ const lang = {
'PaymentCheckoutName': 'Name',
'ClearRecentStickersAlertTitle': 'Clear recent stickers',
'ClearRecentStickersAlertMessage': 'Do you want to clear all your recent stickers?',
'PremiumStickersShort': 'Premium',
// * macos
'AccountSettings.Filters': 'Chat Folders',

10
src/lib/appManagers/appDialogsManager.ts

@ -2028,9 +2028,10 @@ export class AppDialogsManager { @@ -2028,9 +2028,10 @@ export class AppDialogsManager {
autonomous?: boolean,
lazyLoadQueue?: LazyLoadQueue,
loadPromises?: Promise<any>[],
fromName?: string
fromName?: string,
noIcons?: boolean
}) {
return this.addDialog(options.peerId, options.container, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue, options.loadPromises, options.fromName);
return this.addDialog(options.peerId, options.container, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue, options.loadPromises, options.fromName, options.noIcons);
}
public addDialog(
@ -2044,7 +2045,8 @@ export class AppDialogsManager { @@ -2044,7 +2045,8 @@ export class AppDialogsManager {
autonomous = !!container,
lazyLoadQueue?: LazyLoadQueue,
loadPromises?: Promise<any>[],
fromName?: string
fromName?: string,
noIcons?: boolean
) {
// const dialog = await this.getDialog(_dialog);
const avatarEl = new AvatarElement();
@ -2070,7 +2072,7 @@ export class AppDialogsManager { @@ -2070,7 +2072,7 @@ export class AppDialogsManager {
dialog: meAsSaved,
onlyFirstName,
plainText: false,
withIcons: true
withIcons: !noIcons
});
if(loadPromises) {

6
src/lib/appManagers/appImManager.ts

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import animationIntersector from '../../components/animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../../components/animationIntersector';
import appSidebarLeft, {LEFT_COLUMN_ACTIVE_CLASSNAME} from '../../components/sidebarLeft';
import appSidebarRight, {RIGHT_COLUMN_ACTIVE_CLASSNAME} from '../../components/sidebarRight';
import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes';
@ -93,7 +93,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName'; @@ -93,7 +93,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName';
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
import PopupPayment from '../../components/popups/payment';
export const CHAT_ANIMATION_GROUP = 'chat';
export const CHAT_ANIMATION_GROUP: AnimationItemGroup = 'chat';
export type ChatSavedPosition = {
mids: number[],
@ -415,6 +415,8 @@ export class AppImManager extends EventListenerBase<{ @@ -415,6 +415,8 @@ export class AppImManager extends EventListenerBase<{
this.addEventListener('peer_changed', async(peerId) => {
document.body.classList.toggle('has-chat', !!peerId);
this.emojiAnimationContainer.textContent = '';
this.overrideHash(peerId);
apiManagerProxy.updateTabState('chatPeerIds', this.chats.map((chat) => chat.peerId).filter(Boolean));

28
src/lib/appManagers/appManagersManager.ts

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import App from '../../config/app';
import callbackify from '../../helpers/callbackify';
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
import cryptoMessagePort from '../crypto/cryptoMessagePort';
@ -16,10 +17,13 @@ type Managers = Awaited<ReturnType<typeof createManagers>>; @@ -16,10 +17,13 @@ type Managers = Awaited<ReturnType<typeof createManagers>>;
export class AppManagersManager {
private managers: Managers | Promise<Managers>;
private cryptoPortAttached: boolean;
private cryptoWorkersURLs: string[];
private cryptoPortsAttached: number;
private cryptoPortPromise: CancellablePromise<void>;
constructor() {
this.cryptoWorkersURLs = [];
this.cryptoPortsAttached = 0;
this.cryptoPortPromise = deferredPromise();
this.cryptoPortPromise.then(() => {
this.cryptoPortPromise = undefined;
@ -38,14 +42,28 @@ export class AppManagersManager { @@ -38,14 +42,28 @@ export class AppManagersManager {
});
port.addEventListener('cryptoPort', (payload, source, event) => {
if(this.cryptoPortAttached) {
const port = event.ports[0];
if(this.cryptoPortsAttached >= this.cryptoWorkersURLs.length) {
port.close();
return;
}
this.cryptoPortAttached = true;
const port = event.ports[0];
++this.cryptoPortsAttached;
cryptoMessagePort.attachPort(port);
this.cryptoPortPromise.resolve();
this.cryptoPortPromise?.resolve();
return;
});
port.addEventListener('createProxyWorkerURLs', (blob) => {
const length = this.cryptoWorkersURLs.length;
const maxLength = App.cryptoWorkers;
if(length) {
return this.cryptoWorkersURLs;
}
const newURLs = new Array(maxLength - length).fill(undefined).map(() => URL.createObjectURL(blob));
this.cryptoWorkersURLs.push(...newURLs);
return newURLs;
});
}

10
src/lib/appManagers/appMessagesManager.ts

@ -60,6 +60,7 @@ import MTProtoMessagePort from '../mtproto/mtprotoMessagePort'; @@ -60,6 +60,7 @@ import MTProtoMessagePort from '../mtproto/mtprotoMessagePort';
import getAlbumText from './utils/messages/getAlbumText';
import pause from '../../helpers/schedulers/pause';
import makeError from '../../helpers/makeError';
import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb';
// console.trace('include');
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -2091,7 +2092,7 @@ export class AppMessagesManager extends AppManager { @@ -2091,7 +2092,7 @@ export class AppMessagesManager extends AppManager {
keys.forEach((key) => {
// @ts-ignore
message[key] = originalMessage[key];
message[key] = copy(originalMessage[key]);
});
const document = (message.media as MessageMedia.messageMediaDocument)?.document as MyDocument;
@ -2100,6 +2101,13 @@ export class AppMessagesManager extends AppManager { @@ -2100,6 +2101,13 @@ export class AppMessagesManager extends AppManager {
if(types.includes(document.type)) {
(message as MyMessage).pFlags.media_unread = true;
}
if(document.sticker && !this.rootScope.premium) {
const effectThumb = getStickerEffectThumb(document);
if(effectThumb) {
(message.media as MessageMedia.messageMediaDocument).pFlags.nopremium = true;
}
}
}
if(originalMessage.grouped_id) {

12
src/lib/appManagers/appStickersManager.ts

@ -40,9 +40,9 @@ export class AppStickersManager extends AppManager { @@ -40,9 +40,9 @@ export class AppStickersManager extends AppManager {
private storage = new AppStorage<Record<Long, MyMessagesStickerSet>, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets');
private getStickerSetPromises: {[setId: Long]: Promise<MyMessagesStickerSet>};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<MyDocument[]>};
private greetingStickers: Document.document[];
private greetingStickers: MyDocument[];
private getGreetingStickersTimeout: number;
private getGreetingStickersPromise: Promise<void>;
@ -409,6 +409,14 @@ export class AppStickersManager extends AppManager { @@ -409,6 +409,14 @@ export class AppStickersManager extends AppManager {
return res.sets;
}
public async getPromoPremiumStickers() {
return this.getStickersByEmoticon('⭐', false);
}
public async getPremiumStickers() {
return this.getStickersByEmoticon('📂⭐', false);
}
public async toggleStickerSet(set: StickerSet.stickerSet) {
set = this.storage.getFromCache(set.id).set;

15
src/lib/crypto/crypto.worker.ts

@ -22,6 +22,7 @@ import rsaEncrypt from './utils/rsa'; @@ -22,6 +22,7 @@ import rsaEncrypt from './utils/rsa';
import sha1 from './utils/sha1';
import sha256 from './utils/sha256';
import {aesCtrDestroy, aesCtrPrepare, aesCtrProcess} from './aesCtrUtils';
import ctx from '../../environment/ctx';
console.log('CryptoWorker start');
@ -46,10 +47,16 @@ const cryptoMethods: CryptoMethods = { @@ -46,10 +47,16 @@ const cryptoMethods: CryptoMethods = {
'aes-ctr-destroy': aesCtrDestroy
};
cryptoMessagePort.addEventListener('invoke', ({method, args}) => {
// @ts-ignore
const result: any = cryptoMethods[method](...args);
return result;
cryptoMessagePort.addMultipleEventsListeners({
invoke: ({method, args}) => {
// @ts-ignore
const result: any = cryptoMethods[method](...args);
return result;
},
terminate: () => {
ctx.close();
}
});
if(typeof(MessageChannel) !== 'undefined') listenMessagePort(cryptoMessagePort, (source) => {

21
src/lib/crypto/cryptoMessagePort.ts

@ -12,15 +12,23 @@ import {IS_WORKER} from '../../helpers/context'; @@ -12,15 +12,23 @@ import {IS_WORKER} from '../../helpers/context';
type CryptoEvent = {
invoke: <T extends keyof CryptoMethods>(payload: {method: T, args: Parameters<CryptoMethods[T]>}) => ReturnType<CryptoMethods[T]>,
port: (payload: void, source: MessageEventSource, event: MessageEvent) => void
port: (payload: void, source: MessageEventSource, event: MessageEvent) => void,
terminate: () => void
};
export class CryptoMessagePort<Master extends boolean = false> extends SuperMessagePort<CryptoEvent, CryptoEvent, Master> {
private lastIndex: number;
constructor() {
super('CRYPTO');
this.lastIndex = -1;
}
public invokeCrypto<T extends keyof CryptoMethods>(method: T, ...args: Parameters<CryptoMethods[T]>): Promise<Awaited<ReturnType<CryptoMethods[T]>>> {
public invokeCryptoNew<T extends keyof CryptoMethods>({method, args, transfer}: {
method: T,
args: Parameters<CryptoMethods[T]>,
transfer?: Transferable[]
}): Promise<Awaited<ReturnType<CryptoMethods[T]>>> {
const payload = {method, args};
const listeners = this.listeners['invoke'];
if(listeners?.length) { // already in worker
@ -37,8 +45,15 @@ export class CryptoMessagePort<Master extends boolean = false> extends SuperMess @@ -37,8 +45,15 @@ export class CryptoMessagePort<Master extends boolean = false> extends SuperMess
// }
}
const sendPortIndex = method === 'aes-encrypt' || method === 'aes-decrypt' ?
this.lastIndex = (this.lastIndex + 1) % this.sendPorts.length :
0;
// @ts-ignore
return this.invoke('invoke', payload);
return this.invoke('invoke', payload, undefined, this.sendPorts[sendPortIndex], transfer);
}
public invokeCrypto<T extends keyof CryptoMethods>(method: T, ...args: Parameters<CryptoMethods[T]>): Promise<Awaited<ReturnType<CryptoMethods[T]>>> {
return this.invokeCryptoNew({method, args});
}
}

21
src/lib/mtproto/apiFileManager.ts

@ -29,7 +29,7 @@ import MTProtoMessagePort from './mtprotoMessagePort'; @@ -29,7 +29,7 @@ import MTProtoMessagePort from './mtprotoMessagePort';
import getFileNameForUpload from '../../helpers/getFileNameForUpload';
import type {Progress} from '../appManagers/appDownloadManager';
import getDownloadMediaDetails from '../appManagers/utils/download/getDownloadMediaDetails';
import networkStats from './networkStats';
// import networkStats from './networkStats';
import getDownloadFileNameFromOptions from '../appManagers/utils/download/getDownloadFileNameFromOptions';
import StreamWriter from '../files/streamWriter';
import FileStorage from '../files/fileStorage';
@ -89,7 +89,8 @@ const MIN_PART_SIZE = 128 * 1024; @@ -89,7 +89,8 @@ const MIN_PART_SIZE = 128 * 1024;
const AVG_PART_SIZE = 512 * 1024;
const REGULAR_DOWNLOAD_DELTA = (9 * 512 * 1024) / MIN_PART_SIZE;
const PREMIUM_DOWNLOAD_DELTA = REGULAR_DOWNLOAD_DELTA * 2;
// const PREMIUM_DOWNLOAD_DELTA = REGULAR_DOWNLOAD_DELTA * 2;
const PREMIUM_DOWNLOAD_DELTA = (56 * 512 * 1024) / MIN_PART_SIZE;
const IGNORE_ERRORS: Set<ErrorType> = new Set([
'DOWNLOAD_CANCELED',
@ -201,15 +202,15 @@ export class ApiFileManager extends AppManager { @@ -201,15 +202,15 @@ export class ApiFileManager extends AppManager {
this.downloadActives[dcId] += activeDelta;
const promise = data.cb();
const networkPromise = networkStats.waitForChunk(dcId as DcId, activeDelta * MIN_PART_SIZE);
Promise.race([
promise,
networkPromise
]).then(() => {
// const networkPromise = networkStats.waitForChunk(dcId as DcId, activeDelta * MIN_PART_SIZE);
/* Promise.race([
promise
// networkPromise
]) */promise.then(() => {
this.downloadActives[dcId] -= activeDelta;
this.downloadCheck(dcId);
networkPromise.resolve();
// networkPromise.resolve();
}, (error: ApiError) => {
if(!error?.type || !IGNORE_ERRORS.has(error.type)) {
this.log.error('downloadCheck error:', error);
@ -218,7 +219,7 @@ export class ApiFileManager extends AppManager { @@ -218,7 +219,7 @@ export class ApiFileManager extends AppManager {
this.downloadActives[dcId] -= activeDelta;
this.downloadCheck(dcId);
networkPromise.reject(error);
// networkPromise.reject(error);
}).finally(() => {
promise.then(data.deferred.resolve, data.deferred.reject);
});
@ -543,7 +544,7 @@ export class ApiFileManager extends AppManager { @@ -543,7 +544,7 @@ export class ApiFileManager extends AppManager {
}
delete item.writer;
indexOfAndSplice(prepared, item);
// indexOfAndSplice(prepared, item);
});
this.downloadPromises[fileName] = deferred;

1
src/lib/mtproto/mtproto.worker.ts

@ -132,6 +132,7 @@ appTabsManager.start(); @@ -132,6 +132,7 @@ appTabsManager.start();
listenMessagePort(port, (source) => {
appTabsManager.addTab(source);
// port.invokeVoid('hello', undefined, source);
// if(!sentHello) {
// port.invokeVoid('hello', undefined, source);
// sentHello = true;

1
src/lib/mtproto/mtprotoMessagePort.ts

@ -31,6 +31,7 @@ export default class MTProtoMessagePort<Master extends boolean = true> extends S @@ -31,6 +31,7 @@ export default class MTProtoMessagePort<Master extends boolean = true> extends S
cryptoPort: (payload: void, source: MessageEventSource, event: MessageEvent) => void,
createObjectURL: (blob: Blob) => string,
tabState: (payload: TabState, source: MessageEventSource) => void,
createProxyWorkerURLs: (blob: Blob) => string[],
} & MTProtoBroadcastEvent, {
convertWebp: (payload: {fileName: string, bytes: Uint8Array}) => Promise<Uint8Array>,
convertOpus: (payload: {fileName: string, bytes: Uint8Array}) => Promise<Uint8Array>,

72
src/lib/mtproto/mtprotoworker.ts

@ -27,6 +27,7 @@ import IS_SHARED_WORKER_SUPPORTED from '../../environment/sharedWorkerSupport'; @@ -27,6 +27,7 @@ import IS_SHARED_WORKER_SUPPORTED from '../../environment/sharedWorkerSupport';
import toggleStorages from '../../helpers/toggleStorages';
import idleController from '../../helpers/idleController';
import ServiceMessagePort from '../serviceWorker/serviceMessagePort';
import App from '../../config/app';
export type Mirrors = {
state: State
@ -81,6 +82,7 @@ class ApiManagerProxy extends MTProtoMessagePort { @@ -81,6 +82,7 @@ class ApiManagerProxy extends MTProtoMessagePort {
this.registerServiceWorker();
this.registerCryptoWorker();
// const perf = performance.now();
this.addMultipleEventsListeners({
convertWebp: ({fileName, bytes}) => {
return webpWorkerController.convert(fileName, bytes);
@ -101,6 +103,10 @@ class ApiManagerProxy extends MTProtoMessagePort { @@ -101,6 +103,10 @@ class ApiManagerProxy extends MTProtoMessagePort {
},
mirror: this.onMirrorTask
// hello: () => {
// this.log.error('time hello', performance.now() - perf);
// }
});
// this.addTaskListener('socketProxy', (task) => {
@ -276,27 +282,61 @@ class ApiManagerProxy extends MTProtoMessagePort { @@ -276,27 +282,61 @@ class ApiManagerProxy extends MTProtoMessagePort {
});
}
private registerCryptoWorker() {
let worker: SharedWorker | Worker;
if(IS_SHARED_WORKER_SUPPORTED) {
worker = new SharedWorker(
/* webpackChunkName: "crypto.worker" */
new URL('../crypto/crypto.worker.ts', import.meta.url),
{type: 'module'}
);
} else {
worker = new Worker(
/* webpackChunkName: "crypto.worker" */
new URL('../crypto/crypto.worker.ts', import.meta.url),
{type: 'module'}
);
}
private async registerCryptoWorker() {
const get = (url: string) => {
return fetch(url).then((response) => response.text()).then((text) => {
text = 'var a = importScripts; importScripts = (url) => {console.log(`wut`, url); return a(url.slice(5));};' + text;
const blob = new Blob([text], {type: 'application/javascript'});
return blob;
});
};
const workerHandler = {
construct(target: any, args: any): any {
const url = args[0] + location.search;
return {url};
}
};
const originals = [
Worker,
typeof(SharedWorker) !== 'undefined' && SharedWorker
].filter(Boolean);
originals.forEach((w) => window[w.name as any] = new Proxy(w, workerHandler));
const worker: SharedWorker | Worker = new Worker(
/* webpackChunkName: "crypto.worker" */
new URL('../crypto/crypto.worker.ts', import.meta.url),
{type: 'module'}
);
originals.forEach((w) => window[w.name as any] = w as any);
const blob = await get((worker as any).url);
const urlsPromise = await this.invoke('createProxyWorkerURLs', blob);
const workers = urlsPromise.map((url) => {
return new (IS_SHARED_WORKER_SUPPORTED ? SharedWorker : Worker)(url, {type: 'module'});
});
// let cryptoWorkers = workers.length;
cryptoMessagePort.addEventListener('port', (payload, source, event) => {
this.invokeVoid('cryptoPort', undefined, undefined, [event.ports[0]]);
// .then((attached) => {
// if(!attached && cryptoWorkers-- > 1) {
// this.log.error('terminating unneeded crypto worker');
// cryptoMessagePort.invokeVoid('terminate', undefined, source);
// const worker = workers.find((worker) => (worker as SharedWorker).port === source || (worker as any) === source);
// if((worker as SharedWorker).port) (worker as SharedWorker).port.close();
// else (worker as Worker).terminate();
// cryptoMessagePort.detachPort(source);
// }
// });
});
this.attachWorkerToPort(worker, cryptoMessagePort, 'crypto');
workers.forEach((worker) => {
this.attachWorkerToPort(worker, cryptoMessagePort, 'crypto');
});
}
// #if !MTPROTO_SW

13
src/lib/mtproto/networker.ts

@ -112,6 +112,7 @@ const RESEND_OPTIONS: MTMessageOptions = { @@ -112,6 +112,7 @@ const RESEND_OPTIONS: MTMessageOptions = {
notContentRelated: true
};
let invokeAfterMsgConstructor: number;
let networkerTempId = 0;
export default class MTPNetworker {
private authKeyUint8: Uint8Array;
@ -178,6 +179,7 @@ export default class MTPNetworker { @@ -178,6 +179,7 @@ export default class MTPNetworker {
private pingPromise: Promise<void>;
// private pingInterval: number;
private lastPingTime: number;
private lastPingStartTime: number;
private lastPingDelayDisconnectId: string;
// #endif
// public onConnectionStatusChange: (online: boolean) => void;
@ -207,7 +209,7 @@ export default class MTPNetworker { @@ -207,7 +209,7 @@ export default class MTPNetworker {
const suffix = this.isFileUpload ? '-U' : this.isFileDownload ? '-D' : '';
this.name = 'NET-' + dcId + suffix;
// this.log = logger(this.name, this.upload && this.dcId === 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error);
this.log = logger(this.name, LogTypes.Log /* | LogTypes.Debug */ | LogTypes.Error | LogTypes.Warn);
this.log = logger(this.name + (suffix ? '' : '-C') + '-' + networkerTempId++, LogTypes.Log/* | LogTypes.Debug */ | LogTypes.Error | LogTypes.Warn);
this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
// Test resend after bad_server_salt
@ -563,7 +565,7 @@ export default class MTPNetworker { @@ -563,7 +565,7 @@ export default class MTPNetworker {
const lastPingTime = Math.min(this.lastPingTime ?? 0, pingMaxTime);
const disconnectDelay = Math.round(delays.disconnectDelayMin + lastPingTime / pingMaxTime * (delays.disconnectDelayMax - delays.disconnectDelayMin));
const timeoutTime = disconnectDelay * 1000;
const startTime = Date.now();
const startTime = this.lastPingStartTime = Date.now();
const pingId = this.lastPingDelayDisconnectId = randomLong();
const options: MTMessageOptions = {notContentRelated: true};
this.wrapMtpCall('ping_delay_disconnect', {
@ -571,14 +573,15 @@ export default class MTPNetworker { @@ -571,14 +573,15 @@ export default class MTPNetworker {
disconnect_delay: disconnectDelay
}, options);
this.debug && this.log.debug(`sendPingDelayDisconnect: ping, timeout=${timeoutTime}, lastPingTime=${this.lastPingTime}, msgId=${options.messageId}`);
const log = this.log.bindPrefix('sendPingDelayDisconnect');
this.debug && log.debug(`ping, timeout=${timeoutTime}, lastPingTime=${this.lastPingTime}, msgId=${options.messageId}, pingId=${pingId}`);
const rejectTimeout = ctx.setTimeout(deferred.reject, timeoutTime);
const onResolved = (reason: string) => {
clearTimeout(rejectTimeout);
const elapsedTime = Date.now() - startTime;
this.lastPingTime = elapsedTime / 1000;
this.debug && this.log.debug(`sendPingDelayDisconnect: pong, reason='${reason}', time=${lastPingTime}, msgId=${options.messageId}`);
this.debug && log.debug(`pong, reason='${reason}', time=${lastPingTime}, msgId=${options.messageId}`);
if(elapsedTime > timeoutTime) {
throw undefined;
} else {
@ -593,7 +596,7 @@ export default class MTPNetworker { @@ -593,7 +596,7 @@ export default class MTPNetworker {
return;
}
this.log.error('sendPingDelayDisconnect: catch, closing connection', this.lastPingTime, options.messageId);
log.error('catch, closing connection', this.lastPingTime, options.messageId);
transport.connection.close();
};

6
src/lib/mtproto/transports/http.ts

@ -13,7 +13,7 @@ import Modes from '../../../config/modes'; @@ -13,7 +13,7 @@ import Modes from '../../../config/modes';
// #if MTPROTO_AUTO
import transportController from './controller';
import networkStats from '../networkStats';
// import networkStats from '../networkStats';
// #endif
export default class HTTP implements MTTransport {
@ -47,7 +47,7 @@ export default class HTTP implements MTTransport { @@ -47,7 +47,7 @@ export default class HTTP implements MTTransport {
const length = body.length;
this.debug && this.log.debug('-> body length to send:', length);
networkStats.addSent(this.dcId, length);
// networkStats.addSent(this.dcId, length);
return fetch(this.url, {method: 'POST', body, mode}).then((response) => {
if(response.status !== 200 && !mode) {
response.arrayBuffer().then((buffer) => {
@ -66,7 +66,7 @@ export default class HTTP implements MTTransport { @@ -66,7 +66,7 @@ export default class HTTP implements MTTransport {
// }
return response.arrayBuffer().then((buffer) => {
networkStats.addReceived(this.dcId, buffer.byteLength);
// networkStats.addReceived(this.dcId, buffer.byteLength);
return new Uint8Array(buffer);
});
}, (err) => {

9
src/lib/mtproto/transports/obfuscation.ts

@ -157,10 +157,11 @@ export default class Obfuscation { @@ -157,10 +157,11 @@ export default class Obfuscation {
} */
private _process = (data: Uint8Array, operation: 'encrypt' | 'decrypt') => {
return cryptoMessagePort.invoke('invoke', {
return cryptoMessagePort.invokeCryptoNew({
method: 'aes-ctr-process',
args: [{id: this.id, data, operation}]
}, undefined, undefined, [data.buffer]) as Promise<Uint8Array>;
args: [{id: this.id, data, operation}],
transfer: [data.buffer]
}) as Promise<Uint8Array>;
};
public encode(payload: Uint8Array) {
@ -222,4 +223,4 @@ export default class Obfuscation { @@ -222,4 +223,4 @@ export default class Obfuscation {
// return decoded;
// }
}
}

6
src/lib/mtproto/transports/tcpObfuscated.ts

@ -17,7 +17,7 @@ import {ConnectionStatus} from '../connectionStatus'; @@ -17,7 +17,7 @@ import {ConnectionStatus} from '../connectionStatus';
// #if MTPROTO_AUTO
import transportController from './controller';
import bytesToHex from '../../../helpers/bytes/bytesToHex';
import networkStats from '../networkStats';
// import networkStats from '../networkStats';
import ctx from '../../../environment/ctx';
// #endif
@ -94,7 +94,7 @@ export default class TcpObfuscated implements MTTransport { @@ -94,7 +94,7 @@ export default class TcpObfuscated implements MTTransport {
};
private onMessage = async(buffer: ArrayBuffer) => {
networkStats.addReceived(this.dcId, buffer.byteLength);
// networkStats.addReceived(this.dcId, buffer.byteLength);
let data = await this.obfuscation.decode(new Uint8Array(buffer));
data = this.codec.readPacket(data);
@ -343,7 +343,7 @@ export default class TcpObfuscated implements MTTransport { @@ -343,7 +343,7 @@ export default class TcpObfuscated implements MTTransport {
break;
}
networkStats.addSent(this.dcId, encoded.byteLength);
// networkStats.addSent(this.dcId, encoded.byteLength);
this.connection.send(encoded);
if(!pending.resolve) { // remove if no response needed

8
src/lib/rlottie/lottieLoader.ts

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import animationIntersector from '../../components/animationIntersector';
import animationIntersector, {AnimationItemGroup} from '../../components/animationIntersector';
import {MOUNT_CLASS_TO} from '../../config/debug';
import pause from '../../helpers/schedulers/pause';
import {logger, LogTypes} from '../logger';
@ -131,7 +131,11 @@ export class LottieLoader { @@ -131,7 +131,11 @@ export class LottieLoader {
]).then(() => player);
}
public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', middleware?: () => boolean): Promise<RLottiePlayer> {
public async loadAnimationWorker(
params: RLottieOptions,
group: AnimationItemGroup = params.group || '',
middleware?: () => boolean
): Promise<RLottiePlayer> {
if(!this.isWebAssemblySupported) {
return this.loadPromise as any;
}

10
src/lib/rlottie/rlottiePlayer.ts

@ -12,6 +12,7 @@ import mediaSizes from '../../helpers/mediaSizes'; @@ -12,6 +12,7 @@ import mediaSizes from '../../helpers/mediaSizes';
import clamp from '../../helpers/number/clamp';
import lottieLoader from './lottieLoader';
import QueryableWorker from './queryableWorker';
import {AnimationItemGroup} from '../../components/animationIntersector';
export type RLottieOptions = {
container: HTMLElement,
@ -21,7 +22,7 @@ export type RLottieOptions = { @@ -21,7 +22,7 @@ export type RLottieOptions = {
loop?: boolean,
width?: number,
height?: number,
group?: string,
group?: AnimationItemGroup,
noCache?: boolean,
needUpscale?: boolean,
skipRatio?: number,
@ -86,7 +87,8 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -86,7 +87,8 @@ export default class RLottiePlayer extends EventListenerBase<{
enterFrame: (frameNo: number) => void,
ready: () => void,
firstFrame: () => void,
cached: () => void
cached: () => void,
destroy: () => void
}> {
private static reqId = 0;
@ -241,6 +243,7 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -241,6 +243,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.canvas.classList.add('rlottie');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.dpr = pixelRatio;
}
this.context = this.canvas.getContext('2d');
@ -354,8 +357,9 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -354,8 +357,9 @@ export default class RLottiePlayer extends EventListenerBase<{
this.pause();
this.sendQuery('destroy');
if(this.cacheName) cache.releaseCache(this.cacheName);
this.cleanup();
this.dispatchEvent('destroy');
// this.removed = true;
this.cleanup();
}
private applyColor(frame: Uint8ClampedArray) {

8
src/scss/mixins/_premium.scss

@ -4,13 +4,13 @@ @@ -4,13 +4,13 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
@mixin premium($not: false) {
@if $not {
body:not(.is-premium) & {
@mixin premium($yes: true) {
@if $yes {
body.is-premium & {
@content;
}
} @else {
body.is-premium & {
body:not(.is-premium) & {
@content;
}
}

40
src/scss/style.scss

@ -126,6 +126,8 @@ $chat-input-inner-padding-handhelds: .25rem; @@ -126,6 +126,8 @@ $chat-input-inner-padding-handhelds: .25rem;
--peer-avatar-pink-top: #e0a2f3;
--peer-avatar-pink-bottom: #d669ed;
--premium-gradient: linear-gradient(52.62deg, #6B93FF 12.22%, #976FFF 50.25%, #E46ACE 98.83%);
@include respond-to(handhelds) {
--right-column-width: 100vw;
--esg-sticker-size: 68px;
@ -592,6 +594,12 @@ input:-webkit-autofill:active { @@ -592,6 +594,12 @@ input:-webkit-autofill:active {
}
}
.color-premium {
background: var(--premium-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.blue:before, .primary:before, .danger:before {
color: inherit !important;
}
@ -1338,6 +1346,38 @@ middle-ellipsis-element { @@ -1338,6 +1346,38 @@ middle-ellipsis-element {
height: 100%;
max-width: 100%;
max-height: 100%;
&-wrapper {
&.is-premium-sticker {
&:before {
// content: " ";
position: absolute;
bottom: .125rem;
left: 50%;
transform: translateX(-50%);
width: 1.25rem;
height: 1.25rem;
background: rgba(0, 0, 0, .2);
// backdrop-filter: blur(25px) saturate(1.5);
border-radius: 50%;
color: #fff;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
background-image: var(--lock-url);
background-repeat: no-repeat;
background-size: cover;
}
@include premium(true) {
&:before,
&:after {
content: none;
}
}
}
}
}
.media-round {

Loading…
Cancel
Save