Premium stickers
Faster file downloading
This commit is contained in:
parent
0c4a99f67d
commit
b9e6151d5c
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
(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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
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 {
|
||||
avatarSize: 54,
|
||||
meAsSaved: false,
|
||||
message,
|
||||
query
|
||||
query,
|
||||
noIcons: this.noIcons
|
||||
});
|
||||
} catch(err) {
|
||||
console.error('[appSearch] render search result', err);
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
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.type === 'sticker') {
|
||||
emoticonsDropdown.stickersTab?.pushRecentSticker(document);
|
||||
}
|
||||
|
||||
return true;
|
||||
if(!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
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);
|
||||
|
||||
if(document.type === 'sticker') {
|
||||
emoticonsDropdown.stickersTab?.pushRecentSticker(document);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private canToggleHideAuthor() {
|
||||
|
@ -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 {
|
||||
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 {
|
||||
// });
|
||||
|
||||
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) {
|
||||
|
@ -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';
|
||||
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,
|
||||
|
@ -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 {
|
||||
|
||||
constructor(
|
||||
private regularLazyLoadQueue: LazyLoadQueue,
|
||||
private group: string,
|
||||
private group: AnimationItemGroup,
|
||||
private managers: AppManagers,
|
||||
private options?: IntersectionObserverInit
|
||||
) {
|
||||
@ -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 = {
|
||||
items: {
|
||||
document: MyDocument,
|
||||
element: HTMLElement
|
||||
}[]
|
||||
}[],
|
||||
pos?: number
|
||||
};
|
||||
|
||||
const RECENT_STICKERS_COUNT = 20;
|
||||
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
);
|
||||
// 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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
this.setCategoryItemsHeight(category);
|
||||
this.toggleRecentCategory(category, true);
|
||||
this.toggleLocalCategory(category, true);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
@ -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 {
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
private group: string,
|
||||
private group: AnimationItemGroup,
|
||||
private scrollable: Scrollable,
|
||||
attach = true
|
||||
) {
|
||||
|
@ -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';
|
||||
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 {
|
||||
play: true,
|
||||
loop: true,
|
||||
width: size,
|
||||
height: size
|
||||
height: size,
|
||||
withLock: true
|
||||
});
|
||||
|
||||
return div;
|
||||
|
@ -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) => {
|
||||
}
|
||||
}); */
|
||||
|
||||
const SCROLL_THROTTLE = IS_ANDROID ? 200 : 24;
|
||||
|
||||
export class ScrollableBase {
|
||||
protected log: ReturnType<typeof logger>;
|
||||
|
||||
@ -196,7 +199,7 @@ export class ScrollableBase {
|
||||
this.checkForTriggers();
|
||||
}
|
||||
// });
|
||||
}, 200);
|
||||
}, SCROLL_THROTTLE);
|
||||
};
|
||||
|
||||
public cancelMeasure() {
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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';
|
||||
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;
|
||||
|
@ -151,7 +151,8 @@ export default class AppStickersTab extends SliderSuperTab {
|
||||
play: true,
|
||||
loop: true,
|
||||
width: 68,
|
||||
height: 68
|
||||
height: 68,
|
||||
withLock: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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';
|
||||
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';
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
|
||||
// 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,
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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({
|
||||
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({
|
||||
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({
|
||||
const stableOffsetX = /* size / 8 */16 * (side === 'right' ? 1 : -1);
|
||||
const setPosition = () => {
|
||||
if(!isInDOM(target)) {
|
||||
unmountAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -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';
|
||||
};
|
||||
|
@ -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
|
||||
set: StickerSet.stickerSet,
|
||||
lazyLoadQueue: LazyLoadQueue,
|
||||
container: HTMLElement,
|
||||
group: string,
|
||||
group: AnimationItemGroup,
|
||||
autoplay: boolean,
|
||||
width: number,
|
||||
height: number,
|
||||
|
@ -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
|
||||
lazyLoadQueue?: LazyLoadQueue,
|
||||
noInfo?: boolean,
|
||||
noPlayButton?: boolean,
|
||||
group?: string,
|
||||
group?: AnimationItemGroup,
|
||||
onlyPreview?: boolean,
|
||||
withoutPreloader?: boolean,
|
||||
loadPromises?: Promise<any>[],
|
||||
|
@ -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
|
||||
|
@ -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
Normal file
31
src/helpers/computeLockColor.ts
Normal file
@ -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');
|
||||
}
|
@ -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';
|
||||
|
@ -32,7 +32,10 @@ export default class OverlayClickHandler extends EventListenerBase<{
|
||||
return;
|
||||
}
|
||||
|
||||
cancelEvent(e);
|
||||
if(this.listenerOptions?.capture) {
|
||||
cancelEvent(e);
|
||||
}
|
||||
|
||||
this.close();
|
||||
};
|
||||
|
||||
|
@ -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() {
|
||||
[
|
||||
Worker,
|
||||
typeof(SharedWorker) !== 'undefined' && SharedWorker
|
||||
].forEach((w) => {
|
||||
if(!w) return;
|
||||
].filter(Boolean).forEach((w) => {
|
||||
window[w.name as any] = new Proxy(w, workerHandler);
|
||||
});
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
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 {
|
||||
dialog: meAsSaved,
|
||||
onlyFirstName,
|
||||
plainText: false,
|
||||
withIcons: true
|
||||
withIcons: !noIcons
|
||||
});
|
||||
|
||||
if(loadPromises) {
|
||||
|
@ -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';
|
||||
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<{
|
||||
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));
|
||||
|
@ -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>>;
|
||||
|
||||
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 {
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
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 {
|
||||
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) {
|
||||
|
@ -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 {
|
||||
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;
|
||||
|
||||
|
@ -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 = {
|
||||
'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) => {
|
||||
|
@ -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
|
||||
// }
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
delete item.writer;
|
||||
indexOfAndSplice(prepared, item);
|
||||
// indexOfAndSplice(prepared, item);
|
||||
});
|
||||
|
||||
this.downloadPromises[fileName] = deferred;
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
|
@ -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 {
|
||||
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 {
|
||||
},
|
||||
|
||||
mirror: this.onMirrorTask
|
||||
|
||||
// hello: () => {
|
||||
// this.log.error('time hello', performance.now() - perf);
|
||||
// }
|
||||
});
|
||||
|
||||
// this.addTaskListener('socketProxy', (task) => {
|
||||
@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
cryptoMessagePort.addEventListener('port', (payload, source, event) => {
|
||||
this.invokeVoid('cryptoPort', undefined, undefined, [event.ports[0]]);
|
||||
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'});
|
||||
});
|
||||
|
||||
this.attachWorkerToPort(worker, cryptoMessagePort, 'crypto');
|
||||
// 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);
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
workers.forEach((worker) => {
|
||||
this.attachWorkerToPort(worker, cryptoMessagePort, 'crypto');
|
||||
});
|
||||
}
|
||||
|
||||
// #if !MTPROTO_SW
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.error('sendPingDelayDisconnect: catch, closing connection', this.lastPingTime, options.messageId);
|
||||
log.error('catch, closing connection', this.lastPingTime, options.messageId);
|
||||
transport.connection.close();
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
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 {
|
||||
// }
|
||||
|
||||
return response.arrayBuffer().then((buffer) => {
|
||||
networkStats.addReceived(this.dcId, buffer.byteLength);
|
||||
// networkStats.addReceived(this.dcId, buffer.byteLength);
|
||||
return new Uint8Array(buffer);
|
||||
});
|
||||
}, (err) => {
|
||||
|
@ -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 {
|
||||
|
||||
// return decoded;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
};
|
||||
|
||||
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 {
|
||||
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
|
||||
|
@ -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 {
|
||||
]).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;
|
||||
}
|
||||
|
@ -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 = {
|
||||
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<{
|
||||
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<{
|
||||
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<{
|
||||
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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
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…
Reference in New Issue
Block a user