Merge branch 'custom-emoji'
This commit is contained in:
commit
eb1f341b71
|
@ -4,6 +4,7 @@
|
||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {CustomEmojiRendererElement} from '../lib/richTextProcessor/wrapRichText';
|
||||||
import rootScope from '../lib/rootScope';
|
import rootScope from '../lib/rootScope';
|
||||||
import {IS_SAFARI} from '../environment/userAgent';
|
import {IS_SAFARI} from '../environment/userAgent';
|
||||||
import {MOUNT_CLASS_TO} from '../config/debug';
|
import {MOUNT_CLASS_TO} from '../config/debug';
|
||||||
|
@ -13,14 +14,15 @@ import indexOfAndSplice from '../helpers/array/indexOfAndSplice';
|
||||||
import forEachReverse from '../helpers/array/forEachReverse';
|
import forEachReverse from '../helpers/array/forEachReverse';
|
||||||
import idleController from '../helpers/idleController';
|
import idleController from '../helpers/idleController';
|
||||||
import appMediaPlaybackController from './appMediaPlaybackController';
|
import appMediaPlaybackController from './appMediaPlaybackController';
|
||||||
|
import {fastRaf} from '../helpers/schedulers';
|
||||||
|
|
||||||
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
|
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
|
||||||
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
|
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
|
||||||
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS' | 'STICKER-VIEWER';
|
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS' | 'STICKER-VIEWER' | 'EMOJI';
|
||||||
export interface AnimationItem {
|
export interface AnimationItem {
|
||||||
el: HTMLElement,
|
el: HTMLElement,
|
||||||
group: AnimationItemGroup,
|
group: AnimationItemGroup,
|
||||||
animation: RLottiePlayer | HTMLVideoElement
|
animation: RLottiePlayer | HTMLVideoElement | CustomEmojiRendererElement
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AnimationIntersector {
|
export class AnimationIntersector {
|
||||||
|
@ -47,33 +49,35 @@ export class AnimationIntersector {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = this.byGroups[group as AnimationItemGroup].find((p) => p.el === target);
|
const animation = this.byGroups[group as AnimationItemGroup].find((p) => p.el === target);
|
||||||
if(player) {
|
if(!animation) {
|
||||||
if(entry.isIntersecting) {
|
continue;
|
||||||
this.visible.add(player);
|
|
||||||
this.checkAnimation(player, false);
|
|
||||||
|
|
||||||
/* if(animation instanceof HTMLVideoElement && animation.dataset.src) {
|
|
||||||
animation.src = animation.dataset.src;
|
|
||||||
animation.load();
|
|
||||||
} */
|
|
||||||
} else {
|
|
||||||
this.visible.delete(player);
|
|
||||||
this.checkAnimation(player, true);
|
|
||||||
|
|
||||||
const animation = player.animation;
|
|
||||||
if(animation instanceof RLottiePlayer/* && animation.cachingDelta === 2 */) {
|
|
||||||
// console.warn('will clear cache', player);
|
|
||||||
animation.clearCache();
|
|
||||||
}/* else if(animation instanceof HTMLVideoElement && animation.src) {
|
|
||||||
animation.dataset.src = animation.src;
|
|
||||||
animation.src = '';
|
|
||||||
animation.load();
|
|
||||||
} */
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(entry.isIntersecting) {
|
||||||
|
this.visible.add(animation);
|
||||||
|
this.checkAnimation(animation, false);
|
||||||
|
|
||||||
|
/* if(animation instanceof HTMLVideoElement && animation.dataset.src) {
|
||||||
|
animation.src = animation.dataset.src;
|
||||||
|
animation.load();
|
||||||
|
} */
|
||||||
|
} else {
|
||||||
|
this.visible.delete(animation);
|
||||||
|
this.checkAnimation(animation, true);
|
||||||
|
|
||||||
|
const _animation = animation.animation;
|
||||||
|
if(_animation instanceof RLottiePlayer/* && animation.cachingDelta === 2 */) {
|
||||||
|
// console.warn('will clear cache', player);
|
||||||
|
_animation.clearCache();
|
||||||
|
}/* else if(animation instanceof HTMLVideoElement && animation.src) {
|
||||||
|
animation.dataset.src = animation.src;
|
||||||
|
animation.src = '';
|
||||||
|
animation.load();
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -118,7 +122,6 @@ export class AnimationIntersector {
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeAnimation(player: AnimationItem) {
|
public removeAnimation(player: AnimationItem) {
|
||||||
// console.log('destroy animation');
|
|
||||||
const {el, animation} = player;
|
const {el, animation} = player;
|
||||||
animation.remove();
|
animation.remove();
|
||||||
|
|
||||||
|
@ -141,21 +144,21 @@ export class AnimationIntersector {
|
||||||
this.visible.delete(player);
|
this.visible.delete(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group: AnimationItemGroup = '') {
|
public addAnimation(_animation: AnimationItem['animation'], group: AnimationItemGroup = '') {
|
||||||
const player: AnimationItem = {
|
const animation: AnimationItem = {
|
||||||
el: animation instanceof RLottiePlayer ? animation.el : animation,
|
el: _animation instanceof RLottiePlayer ? _animation.el[0] : (_animation instanceof HTMLVideoElement ? _animation : _animation.canvas),
|
||||||
animation: animation,
|
animation: _animation,
|
||||||
group
|
group
|
||||||
};
|
};
|
||||||
|
|
||||||
if(animation instanceof RLottiePlayer) {
|
if(_animation instanceof RLottiePlayer) {
|
||||||
if(!rootScope.settings.stickers.loop && animation.loop) {
|
if(!rootScope.settings.stickers.loop && _animation.loop) {
|
||||||
animation.loop = rootScope.settings.stickers.loop;
|
_animation.loop = rootScope.settings.stickers.loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(this.byGroups[group as AnimationItemGroup] ??= []).push(player);
|
(this.byGroups[group as AnimationItemGroup] ??= []).push(animation);
|
||||||
this.observer.observe(player.el);
|
this.observer.observe(animation.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
|
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
|
||||||
|
@ -171,8 +174,8 @@ export class AnimationIntersector {
|
||||||
for(const group of groups) {
|
for(const group of groups) {
|
||||||
const animations = this.byGroups[group];
|
const animations = this.byGroups[group];
|
||||||
|
|
||||||
forEachReverse(animations, (player) => {
|
forEachReverse(animations, (animation) => {
|
||||||
this.checkAnimation(player, blurred, destroy);
|
this.checkAnimation(animation, blurred, destroy);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +183,7 @@ export class AnimationIntersector {
|
||||||
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
|
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
|
||||||
const {el, animation, group} = player;
|
const {el, animation, group} = player;
|
||||||
// return;
|
// return;
|
||||||
if((destroy || (!isInDOM(el) && !this.lockedGroups[group]))/* && false */) {
|
if(destroy || (!this.lockedGroups[group] && !isInDOM(el))) {
|
||||||
this.removeAnimation(player);
|
this.removeAnimation(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -220,17 +223,19 @@ export class AnimationIntersector {
|
||||||
|
|
||||||
public refreshGroup(group: AnimationItemGroup) {
|
public refreshGroup(group: AnimationItemGroup) {
|
||||||
const animations = this.byGroups[group];
|
const animations = this.byGroups[group];
|
||||||
if(animations && animations.length) {
|
if(!animations?.length) {
|
||||||
animations.forEach((animation) => {
|
return;
|
||||||
this.observer.unobserve(animation.el);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
animations.forEach((animation) => {
|
|
||||||
this.observer.observe(animation.el);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
animations.forEach((animation) => {
|
||||||
|
this.observer.unobserve(animation.el);
|
||||||
|
});
|
||||||
|
|
||||||
|
fastRaf(() => {
|
||||||
|
animations.forEach((animation) => {
|
||||||
|
this.observer.observe(animation.el);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public lockIntersectionGroup(group: AnimationItemGroup) {
|
public lockIntersectionGroup(group: AnimationItemGroup) {
|
||||||
|
@ -244,7 +249,5 @@ export class AnimationIntersector {
|
||||||
}
|
}
|
||||||
|
|
||||||
const animationIntersector = new AnimationIntersector();
|
const animationIntersector = new AnimationIntersector();
|
||||||
if(MOUNT_CLASS_TO) {
|
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.animationIntersector = animationIntersector);
|
||||||
MOUNT_CLASS_TO.animationIntersector = animationIntersector;
|
|
||||||
}
|
|
||||||
export default animationIntersector;
|
export default animationIntersector;
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
||||||
this.content.main.prepend(stub); */
|
this.content.main.prepend(stub); */
|
||||||
|
|
||||||
this.content.caption = document.createElement('div');
|
this.content.caption = document.createElement('div');
|
||||||
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption', 'message'/* , 'media-viewer-stub' */);
|
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption', 'spoilers-container'/* , 'media-viewer-stub' */);
|
||||||
|
|
||||||
let captionTimeout: number;
|
let captionTimeout: number;
|
||||||
const setCaptionTimeout = () => {
|
const setCaptionTimeout = () => {
|
||||||
|
|
|
@ -113,6 +113,7 @@ import PopupPayment from '../popups/payment';
|
||||||
import isInDOM from '../../helpers/dom/isInDOM';
|
import isInDOM from '../../helpers/dom/isInDOM';
|
||||||
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||||
import attachStickerViewerListeners from '../stickerViewer';
|
import attachStickerViewerListeners from '../stickerViewer';
|
||||||
|
import {makeMediaSize, MediaSize} from '../../helpers/mediaSize';
|
||||||
|
|
||||||
const USE_MEDIA_TAILS = false;
|
const USE_MEDIA_TAILS = false;
|
||||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||||
|
@ -3434,7 +3435,7 @@ export default class ChatBubbles {
|
||||||
const our = this.chat.isOurMessage(message);
|
const our = this.chat.isOurMessage(message);
|
||||||
|
|
||||||
const messageDiv = document.createElement('div');
|
const messageDiv = document.createElement('div');
|
||||||
messageDiv.classList.add('message');
|
messageDiv.classList.add('message', 'spoilers-container');
|
||||||
|
|
||||||
const contentWrapper = document.createElement('div');
|
const contentWrapper = document.createElement('div');
|
||||||
contentWrapper.classList.add('bubble-content-wrapper');
|
contentWrapper.classList.add('bubble-content-wrapper');
|
||||||
|
@ -3526,54 +3527,68 @@ export default class ChatBubbles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* let richText = wrapRichText(messageMessage, {
|
let bigEmojis = 0, customEmojiSize: MediaSize;
|
||||||
entities: totalEntities
|
if(totalEntities && !messageMedia) {
|
||||||
}); */
|
const emojiEntities = totalEntities.filter((e) => e._ === 'messageEntityEmoji'/* || e._ === 'messageEntityCustomEmoji' */);
|
||||||
|
const strLength = messageMessage.replace(/\s/g, '').length;
|
||||||
|
const emojiStrLength = emojiEntities.reduce((acc, curr) => acc + curr.length, 0);
|
||||||
|
|
||||||
|
if(emojiStrLength === strLength /* && emojiEntities.length <= 3 *//* && totalEntities.length === emojiEntities.length */) {
|
||||||
|
bigEmojis = Math.min(4, emojiEntities.length);
|
||||||
|
|
||||||
|
customEmojiSize = mediaSizes.active.customEmoji;
|
||||||
|
const sizes: {[size: number]: number} = {
|
||||||
|
1: 96,
|
||||||
|
2: 64,
|
||||||
|
3: 52,
|
||||||
|
4: 36
|
||||||
|
};
|
||||||
|
|
||||||
|
const size = sizes[bigEmojis];
|
||||||
|
if(size) {
|
||||||
|
customEmojiSize = makeMediaSize(size, size);
|
||||||
|
bubble.style.setProperty('--emoji-size', size + 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const richText = wrapRichText(messageMessage, {
|
const richText = wrapRichText(messageMessage, {
|
||||||
entities: totalEntities,
|
entities: totalEntities,
|
||||||
passEntities: this.passEntities
|
passEntities: this.passEntities,
|
||||||
|
loadPromises,
|
||||||
|
lazyLoadQueue: this.lazyLoadQueue,
|
||||||
|
customEmojiSize
|
||||||
});
|
});
|
||||||
|
|
||||||
let canHaveTail = true;
|
let canHaveTail = true;
|
||||||
let isStandaloneMedia = false;
|
let isStandaloneMedia = false;
|
||||||
let needToSetHTML = true;
|
let needToSetHTML = true;
|
||||||
if(totalEntities && !messageMedia) {
|
if(bigEmojis) {
|
||||||
const emojiEntities = totalEntities.filter((e) => e._ === 'messageEntityEmoji');
|
if(rootScope.settings.emoji.big) {
|
||||||
const strLength = messageMessage.length;
|
const sticker = bigEmojis === 1 &&
|
||||||
const emojiStrLength = emojiEntities.reduce((acc, curr) => acc + curr.length, 0);
|
!totalEntities.find((entity) => entity._ === 'messageEntityCustomEmoji') &&
|
||||||
|
await this.managers.appStickersManager.getAnimatedEmojiSticker(messageMessage);
|
||||||
|
if(bigEmojis === 1 && !messageMedia && sticker) {
|
||||||
|
messageMedia = {
|
||||||
|
_: 'messageMediaDocument',
|
||||||
|
document: sticker
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const attachmentDiv = document.createElement('div');
|
||||||
|
attachmentDiv.classList.add('attachment', 'spoilers-container');
|
||||||
|
|
||||||
if(emojiStrLength === strLength && emojiEntities.length <= 3 && totalEntities.length === emojiEntities.length) {
|
setInnerHTML(attachmentDiv, richText);
|
||||||
if(rootScope.settings.emoji.big) {
|
|
||||||
const sticker = await this.managers.appStickersManager.getAnimatedEmojiSticker(messageMessage);
|
|
||||||
if(emojiEntities.length === 1 && !messageMedia && sticker) {
|
|
||||||
messageMedia = {
|
|
||||||
_: 'messageMediaDocument',
|
|
||||||
document: sticker
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const attachmentDiv = document.createElement('div');
|
|
||||||
attachmentDiv.classList.add('attachment');
|
|
||||||
|
|
||||||
setInnerHTML(attachmentDiv, richText);
|
bubbleContainer.append(attachmentDiv);
|
||||||
|
|
||||||
bubble.classList.add('emoji-' + emojiEntities.length + 'x');
|
|
||||||
|
|
||||||
bubbleContainer.append(attachmentDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
bubble.classList.add('is-message-empty', 'emoji-big');
|
|
||||||
isStandaloneMedia = true;
|
|
||||||
canHaveTail = false;
|
|
||||||
needToSetHTML = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bubble.classList.add('can-have-big-emoji');
|
bubble.classList.add('is-message-empty', 'emoji-big');
|
||||||
|
isStandaloneMedia = true;
|
||||||
|
canHaveTail = false;
|
||||||
|
needToSetHTML = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if(strLength === emojiStrLength) {
|
bubble.classList.add('can-have-big-emoji');
|
||||||
messageDiv.classList.add('emoji-only');
|
|
||||||
messageDiv.classList.add('message-empty');
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(needToSetHTML) {
|
if(needToSetHTML) {
|
||||||
|
|
|
@ -50,10 +50,10 @@ export default class TrackingMonkey {
|
||||||
|
|
||||||
if(this.idleAnimation) {
|
if(this.idleAnimation) {
|
||||||
this.idleAnimation.stop(true);
|
this.idleAnimation.stop(true);
|
||||||
this.idleAnimation.canvas.style.display = 'none';
|
this.idleAnimation.canvas[0].style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.animation.canvas.style.display = '';
|
this.animation.canvas[0].style.display = '';
|
||||||
} else {
|
} else {
|
||||||
/* const cb = (frameNo: number) => {
|
/* const cb = (frameNo: number) => {
|
||||||
if(frameNo <= 1) { */
|
if(frameNo <= 1) { */
|
||||||
|
@ -116,7 +116,7 @@ export default class TrackingMonkey {
|
||||||
this.animation = _animation;
|
this.animation = _animation;
|
||||||
|
|
||||||
if(!this.inputField.value.length) {
|
if(!this.inputField.value.length) {
|
||||||
this.animation.canvas.style.display = 'none';
|
this.animation.canvas[0].style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.animation.addEventListener('enterFrame', currentFrame => {
|
this.animation.addEventListener('enterFrame', currentFrame => {
|
||||||
|
@ -133,9 +133,9 @@ export default class TrackingMonkey {
|
||||||
// animation.curFrame = 0;
|
// animation.curFrame = 0;
|
||||||
|
|
||||||
if(this.idleAnimation) {
|
if(this.idleAnimation) {
|
||||||
this.idleAnimation.canvas.style.display = '';
|
this.idleAnimation.canvas[0].style.display = '';
|
||||||
this.idleAnimation.play();
|
this.idleAnimation.play();
|
||||||
this.animation.canvas.style.display = 'none';
|
this.animation.canvas[0].style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ import getPreviewURLFromThumb from '../../helpers/getPreviewURLFromThumb';
|
||||||
import makeError from '../../helpers/makeError';
|
import makeError from '../../helpers/makeError';
|
||||||
import {makeMediaSize} from '../../helpers/mediaSize';
|
import {makeMediaSize} from '../../helpers/mediaSize';
|
||||||
import mediaSizes from '../../helpers/mediaSizes';
|
import mediaSizes from '../../helpers/mediaSizes';
|
||||||
|
import noop from '../../helpers/noop';
|
||||||
import onMediaLoad from '../../helpers/onMediaLoad';
|
import onMediaLoad from '../../helpers/onMediaLoad';
|
||||||
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
|
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
|
||||||
import throttle from '../../helpers/schedulers/throttle';
|
import throttle from '../../helpers/schedulers/throttle';
|
||||||
|
@ -32,7 +33,6 @@ import getServerMessageId from '../../lib/appManagers/utils/messageId/getServerM
|
||||||
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
|
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
|
||||||
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||||
import lottieLoader from '../../lib/rlottie/lottieLoader';
|
import lottieLoader from '../../lib/rlottie/lottieLoader';
|
||||||
import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
|
|
||||||
import rootScope from '../../lib/rootScope';
|
import rootScope from '../../lib/rootScope';
|
||||||
import type {ThumbCache} from '../../lib/storages/thumbs';
|
import type {ThumbCache} from '../../lib/storages/thumbs';
|
||||||
import webpWorkerController from '../../lib/webp/webpWorkerController';
|
import webpWorkerController from '../../lib/webp/webpWorkerController';
|
||||||
|
@ -50,11 +50,13 @@ const EMOJI_EFFECT_MULTIPLIER = 3;
|
||||||
|
|
||||||
const locksUrls: {[docId: string]: string} = {};
|
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, relativeEffect, loopEffect}: {
|
export default async function wrapSticker({doc, div, middleware, loadStickerMiddleware, lazyLoadQueue, exportLoad, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut, noPremium, withLock, relativeEffect, loopEffect, isCustomEmoji}: {
|
||||||
doc: MyDocument,
|
doc: MyDocument,
|
||||||
div: HTMLElement,
|
div: HTMLElement | HTMLElement[],
|
||||||
middleware?: () => boolean,
|
middleware?: () => boolean,
|
||||||
|
loadStickerMiddleware?: () => boolean,
|
||||||
lazyLoadQueue?: LazyLoadQueue,
|
lazyLoadQueue?: LazyLoadQueue,
|
||||||
|
exportLoad?: boolean,
|
||||||
group?: AnimationItemGroup,
|
group?: AnimationItemGroup,
|
||||||
play?: boolean,
|
play?: boolean,
|
||||||
onlyThumb?: boolean,
|
onlyThumb?: boolean,
|
||||||
|
@ -74,8 +76,11 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
noPremium?: boolean,
|
noPremium?: boolean,
|
||||||
withLock?: boolean,
|
withLock?: boolean,
|
||||||
relativeEffect?: boolean,
|
relativeEffect?: boolean,
|
||||||
loopEffect?: boolean
|
loopEffect?: boolean,
|
||||||
|
isCustomEmoji?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
div = Array.isArray(div) ? div : [div];
|
||||||
|
|
||||||
const stickerType = doc.sticker;
|
const stickerType = doc.sticker;
|
||||||
if(stickerType === 1) {
|
if(stickerType === 1) {
|
||||||
asStatic = true;
|
asStatic = true;
|
||||||
|
@ -94,13 +99,10 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
lottieLoader.loadLottieWorkers();
|
lottieLoader.loadLottieWorkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!stickerType) {
|
div.forEach((div) => {
|
||||||
console.error('wrong doc for wrapSticker!', doc);
|
div.dataset.docId = '' + doc.id;
|
||||||
throw new Error('wrong doc for wrapSticker!');
|
div.classList.add('media-sticker-wrapper');
|
||||||
}
|
});
|
||||||
|
|
||||||
div.dataset.docId = '' + doc.id;
|
|
||||||
div.classList.add('media-sticker-wrapper');
|
|
||||||
|
|
||||||
/* if(stickerType === 3) {
|
/* if(stickerType === 3) {
|
||||||
const videoRes = wrapVideo({
|
const videoRes = wrapVideo({
|
||||||
|
@ -141,14 +143,16 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
|
|
||||||
const effectThumb = getStickerEffectThumb(doc);
|
const effectThumb = getStickerEffectThumb(doc);
|
||||||
if(isOut !== undefined && effectThumb && !isOut) {
|
if(isOut !== undefined && effectThumb && !isOut) {
|
||||||
div.classList.add('reflect-x');
|
div.forEach((div) => div.classList.add('reflect-x'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const willHaveLock = effectThumb && withLock;
|
const willHaveLock = effectThumb && withLock;
|
||||||
if(willHaveLock) {
|
if(willHaveLock) {
|
||||||
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
|
|
||||||
const lockUrl = locksUrls[doc.id];
|
const lockUrl = locksUrls[doc.id];
|
||||||
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
div.forEach((div) => {
|
||||||
|
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
|
||||||
|
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(asStatic && stickerType !== 1) {
|
if(asStatic && stickerType !== 1) {
|
||||||
|
@ -164,13 +168,14 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
const isThumbNeededForType = isAnimated;
|
const isThumbNeededForType = isAnimated;
|
||||||
const lottieCachedThumb = stickerType === 2 || stickerType === 3 ? await managers.appDocsManager.getLottieCachedThumb(doc.id, toneIndex) : undefined;
|
const lottieCachedThumb = stickerType === 2 || stickerType === 3 ? await managers.appDocsManager.getLottieCachedThumb(doc.id, toneIndex) : undefined;
|
||||||
|
|
||||||
|
const ret = {render: undefined as typeof loadPromise, load: undefined as typeof load};
|
||||||
let loadThumbPromise = deferredPromise<void>();
|
let loadThumbPromise = deferredPromise<void>();
|
||||||
let haveThumbCached = false;
|
let haveThumbCached = false;
|
||||||
if((
|
if((
|
||||||
doc.thumbs?.length ||
|
doc.thumbs?.length ||
|
||||||
lottieCachedThumb
|
lottieCachedThumb
|
||||||
) &&
|
) &&
|
||||||
!div.firstElementChild && (
|
!div[0].firstElementChild && (
|
||||||
!downloaded ||
|
!downloaded ||
|
||||||
isThumbNeededForType ||
|
isThumbNeededForType ||
|
||||||
onlyThumb
|
onlyThumb
|
||||||
|
@ -180,8 +185,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
|
|
||||||
// console.log('wrap sticker', thumb, div);
|
// console.log('wrap sticker', thumb, div);
|
||||||
|
|
||||||
let thumbImage: HTMLImageElement | HTMLCanvasElement;
|
const afterRender = (div: HTMLElement, thumbImage: HTMLElement) => {
|
||||||
const afterRender = () => {
|
|
||||||
if(!div.childElementCount) {
|
if(!div.childElementCount) {
|
||||||
thumbImage.classList.add('media-sticker', 'thumbnail');
|
thumbImage.classList.add('media-sticker', 'thumbnail');
|
||||||
|
|
||||||
|
@ -193,105 +197,133 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if('url' in thumb) {
|
if('url' in thumb) {
|
||||||
thumbImage = new Image();
|
|
||||||
renderImageFromUrl(thumbImage, thumb.url, afterRender);
|
|
||||||
haveThumbCached = true;
|
haveThumbCached = true;
|
||||||
|
div.forEach((div) => {
|
||||||
|
const thumbImage = new Image();
|
||||||
|
renderImageFromUrl(thumbImage, (thumb as any).url, () => afterRender(div, thumbImage));
|
||||||
|
});
|
||||||
} else if('bytes' in thumb) {
|
} else if('bytes' in thumb) {
|
||||||
if(thumb._ === 'photoPathSize') {
|
if(thumb._ === 'photoPathSize') {
|
||||||
if(thumb.bytes.length) {
|
if(!thumb.bytes.length) {
|
||||||
const d = getPathFromBytes(thumb.bytes);
|
|
||||||
const ns = 'http://www.w3.org/2000/svg';
|
|
||||||
const svg = document.createElementNS(ns, 'svg');
|
|
||||||
svg.classList.add('rlottie-vector', 'media-sticker', 'thumbnail');
|
|
||||||
svg.setAttributeNS(null, 'viewBox', `0 0 ${doc.w || 512} ${doc.h || 512}`);
|
|
||||||
|
|
||||||
// const defs = document.createElementNS(ns, 'defs');
|
|
||||||
// const linearGradient = document.createElementNS(ns, 'linearGradient');
|
|
||||||
// linearGradient.setAttributeNS(null, 'id', 'g');
|
|
||||||
// linearGradient.setAttributeNS(null, 'x1', '-300%');
|
|
||||||
// linearGradient.setAttributeNS(null, 'x2', '-200%');
|
|
||||||
// linearGradient.setAttributeNS(null, 'y1', '0');
|
|
||||||
// linearGradient.setAttributeNS(null, 'y2', '0');
|
|
||||||
// const stops = [
|
|
||||||
// ['-10%', '.1'],
|
|
||||||
// ['30%', '.07'],
|
|
||||||
// ['70%', '.07'],
|
|
||||||
// ['110%', '.1']
|
|
||||||
// ].map(([offset, stopOpacity]) => {
|
|
||||||
// const stop = document.createElementNS(ns, 'stop');
|
|
||||||
// stop.setAttributeNS(null, 'offset', offset);
|
|
||||||
// stop.setAttributeNS(null, 'stop-opacity', stopOpacity);
|
|
||||||
// return stop;
|
|
||||||
// });
|
|
||||||
// const animates = [
|
|
||||||
// ['-300%', '1200%'],
|
|
||||||
// ['-200%', '1300%']
|
|
||||||
// ].map(([from, to], idx) => {
|
|
||||||
// const animate = document.createElementNS(ns, 'animate');
|
|
||||||
// animate.setAttributeNS(null, 'attributeName', 'x' + (idx + 1));
|
|
||||||
// animate.setAttributeNS(null, 'from', from);
|
|
||||||
// animate.setAttributeNS(null, 'to', to);
|
|
||||||
// animate.setAttributeNS(null, 'dur', '3s');
|
|
||||||
// animate.setAttributeNS(null, 'repeatCount', 'indefinite');
|
|
||||||
// return animate;
|
|
||||||
// });
|
|
||||||
// linearGradient.append(...stops, ...animates);
|
|
||||||
// defs.append(linearGradient);
|
|
||||||
// svg.append(defs);
|
|
||||||
|
|
||||||
const path = document.createElementNS(ns, 'path');
|
|
||||||
path.setAttributeNS(null, 'd', d);
|
|
||||||
if(rootScope.settings.animationsEnabled) path.setAttributeNS(null, 'fill', 'url(#g)');
|
|
||||||
svg.append(path);
|
|
||||||
div.append(svg);
|
|
||||||
} else {
|
|
||||||
thumb = doc.thumbs.find((t) => (t as PhotoSize.photoStrippedSize).bytes?.length) || thumb;
|
thumb = doc.thumbs.find((t) => (t as PhotoSize.photoStrippedSize).bytes?.length) || thumb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const d = getPathFromBytes((thumb as PhotoSize.photoStrippedSize).bytes);
|
||||||
|
const ns = 'http://www.w3.org/2000/svg';
|
||||||
|
const svg = document.createElementNS(ns, 'svg');
|
||||||
|
svg.classList.add('rlottie-vector', 'media-sticker', 'thumbnail');
|
||||||
|
svg.setAttributeNS(null, 'viewBox', `0 0 ${doc.w || 512} ${doc.h || 512}`);
|
||||||
|
|
||||||
|
// const defs = document.createElementNS(ns, 'defs');
|
||||||
|
// const linearGradient = document.createElementNS(ns, 'linearGradient');
|
||||||
|
// linearGradient.setAttributeNS(null, 'id', 'g');
|
||||||
|
// linearGradient.setAttributeNS(null, 'x1', '-300%');
|
||||||
|
// linearGradient.setAttributeNS(null, 'x2', '-200%');
|
||||||
|
// linearGradient.setAttributeNS(null, 'y1', '0');
|
||||||
|
// linearGradient.setAttributeNS(null, 'y2', '0');
|
||||||
|
// const stops = [
|
||||||
|
// ['-10%', '.1'],
|
||||||
|
// ['30%', '.07'],
|
||||||
|
// ['70%', '.07'],
|
||||||
|
// ['110%', '.1']
|
||||||
|
// ].map(([offset, stopOpacity]) => {
|
||||||
|
// const stop = document.createElementNS(ns, 'stop');
|
||||||
|
// stop.setAttributeNS(null, 'offset', offset);
|
||||||
|
// stop.setAttributeNS(null, 'stop-opacity', stopOpacity);
|
||||||
|
// return stop;
|
||||||
|
// });
|
||||||
|
// const animates = [
|
||||||
|
// ['-300%', '1200%'],
|
||||||
|
// ['-200%', '1300%']
|
||||||
|
// ].map(([from, to], idx) => {
|
||||||
|
// const animate = document.createElementNS(ns, 'animate');
|
||||||
|
// animate.setAttributeNS(null, 'attributeName', 'x' + (idx + 1));
|
||||||
|
// animate.setAttributeNS(null, 'from', from);
|
||||||
|
// animate.setAttributeNS(null, 'to', to);
|
||||||
|
// animate.setAttributeNS(null, 'dur', '3s');
|
||||||
|
// animate.setAttributeNS(null, 'repeatCount', 'indefinite');
|
||||||
|
// return animate;
|
||||||
|
// });
|
||||||
|
// linearGradient.append(...stops, ...animates);
|
||||||
|
// defs.append(linearGradient);
|
||||||
|
// svg.append(defs);
|
||||||
|
|
||||||
|
const path = document.createElementNS(ns, 'path');
|
||||||
|
path.setAttributeNS(null, 'd', d);
|
||||||
|
if(rootScope.settings.animationsEnabled && !isCustomEmoji) path.setAttributeNS(null, 'fill', 'url(#g)');
|
||||||
|
svg.append(path);
|
||||||
|
div.forEach((div, idx) => div.append(idx > 0 ? svg.cloneNode(true) : svg));
|
||||||
|
haveThumbCached = true;
|
||||||
|
loadThumbPromise.resolve();
|
||||||
} else if(toneIndex <= 0) {
|
} else if(toneIndex <= 0) {
|
||||||
thumbImage = new Image();
|
const r = () => {
|
||||||
|
(div as HTMLElement[]).forEach((div) => {
|
||||||
|
const thumbImage = new Image();
|
||||||
|
const url = getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
||||||
|
renderImageFromUrl(thumbImage, url, () => afterRender(div, thumbImage));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if((IS_WEBP_SUPPORTED || doc.pFlags.stickerThumbConverted || cacheContext.url)/* && false */) {
|
if((IS_WEBP_SUPPORTED || doc.pFlags.stickerThumbConverted || cacheContext.url)/* && false */) {
|
||||||
renderImageFromUrl(thumbImage, getPreviewURLFromThumb(doc, thumb, true), afterRender);
|
|
||||||
haveThumbCached = true;
|
haveThumbCached = true;
|
||||||
|
r();
|
||||||
} else {
|
} else {
|
||||||
|
haveThumbCached = true;
|
||||||
webpWorkerController.convert('main-' + doc.id, thumb.bytes).then((bytes) => {
|
webpWorkerController.convert('main-' + doc.id, thumb.bytes).then((bytes) => {
|
||||||
managers.appDocsManager.saveWebPConvertedStrippedThumb(doc.id, bytes);
|
managers.appDocsManager.saveWebPConvertedStrippedThumb(doc.id, bytes);
|
||||||
(thumb as PhotoSize.photoStrippedSize).bytes = bytes;
|
(thumb as PhotoSize.photoStrippedSize).bytes = bytes;
|
||||||
doc.pFlags.stickerThumbConverted = true;
|
doc.pFlags.stickerThumbConverted = true;
|
||||||
|
|
||||||
if(middleware && !middleware()) return;
|
if((middleware && !middleware()) || (div as HTMLElement[])[0].childElementCount) {
|
||||||
|
loadThumbPromise.resolve();
|
||||||
if(!div.childElementCount) {
|
return;
|
||||||
renderImageFromUrl(thumbImage, getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true), afterRender);
|
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
|
||||||
|
r();
|
||||||
|
}).catch(() => loadThumbPromise.resolve());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(((stickerType === 2 && toneIndex <= 0) || stickerType === 3) && (withThumb || onlyThumb)) {
|
} else if(((stickerType === 2 && toneIndex <= 0) || stickerType === 3) && (withThumb || onlyThumb)) {
|
||||||
const load = async() => {
|
const load = async() => {
|
||||||
if(div.childElementCount || (middleware && !middleware())) return;
|
if((div as HTMLElement[])[0].childElementCount || (middleware && !middleware())) {
|
||||||
|
loadThumbPromise.resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const r = () => {
|
const r = (div: HTMLElement, thumbImage: HTMLElement) => {
|
||||||
if(div.childElementCount || (middleware && !middleware())) return;
|
if(div.childElementCount || (middleware && !middleware())) {
|
||||||
renderImageFromUrl(thumbImage, cacheContext.url, afterRender);
|
loadThumbPromise.resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderImageFromUrl(thumbImage, cacheContext.url, () => afterRender(div, thumbImage));
|
||||||
};
|
};
|
||||||
|
|
||||||
await getCacheContext();
|
await getCacheContext();
|
||||||
if(cacheContext.url) {
|
(div as HTMLElement[]).forEach((div) => {
|
||||||
r();
|
if(cacheContext.url) {
|
||||||
return;
|
r(div, new Image());
|
||||||
} else {
|
} else if('bytes' in thumb) {
|
||||||
const res = getImageFromStrippedThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
const res = getImageFromStrippedThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
||||||
thumbImage = res.image;
|
res.loadPromise.then(() => r(div, res.image));
|
||||||
res.loadPromise.then(r);
|
|
||||||
|
|
||||||
// return managers.appDocsManager.getThumbURL(doc, thumb as PhotoSize.photoStrippedSize).promise.then(r);
|
// return managers.appDocsManager.getThumbURL(doc, thumb as PhotoSize.photoStrippedSize).promise.then(r);
|
||||||
}
|
} else {
|
||||||
|
appDownloadManager.downloadMediaURL({
|
||||||
|
media: doc,
|
||||||
|
thumb: thumb as PhotoSize
|
||||||
|
}).then(async() => {
|
||||||
|
await getCacheContext();
|
||||||
|
return r(div, new Image());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if(lazyLoadQueue && onlyThumb) {
|
if(lazyLoadQueue && onlyThumb) {
|
||||||
lazyLoadQueue.push({div, load});
|
lazyLoadQueue.push({div: div[0], load});
|
||||||
return;
|
loadThumbPromise.resolve();
|
||||||
|
return ret;
|
||||||
} else {
|
} else {
|
||||||
load();
|
load();
|
||||||
|
|
||||||
|
@ -307,7 +339,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
if(onlyThumb/* || true */) { // for sticker panel
|
if(onlyThumb/* || true */) { // for sticker panel
|
||||||
return;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const middlewareError = makeError('MIDDLEWARE');
|
const middlewareError = makeError('MIDDLEWARE');
|
||||||
|
@ -317,27 +349,14 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stickerType === 2 && !asStatic) {
|
if(stickerType === 2 && !asStatic) {
|
||||||
/* if(doc.id === '1860749763008266301') {
|
return appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId, thumb: fullThumb})
|
||||||
console.log('loaded sticker:', doc, div);
|
|
||||||
} */
|
|
||||||
|
|
||||||
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// console.time('download sticker' + doc.id);
|
|
||||||
|
|
||||||
// appDocsManager.downloadDocNew(doc.id).promise.then((res) => res.json()).then(async(json) => {
|
|
||||||
// fetch(doc.url).then((res) => res.json()).then(async(json) => {
|
|
||||||
return await appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId, thumb: fullThumb})
|
|
||||||
.then(async(blob) => {
|
.then(async(blob) => {
|
||||||
// console.timeEnd('download sticker' + doc.id);
|
|
||||||
// console.log('loaded sticker:', doc, div/* , blob */);
|
|
||||||
if(middleware && !middleware()) {
|
if(middleware && !middleware()) {
|
||||||
throw middlewareError;
|
throw middlewareError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const animation = await lottieLoader.loadAnimationWorker({
|
const animation = await lottieLoader.loadAnimationWorker({
|
||||||
container: div,
|
container: (div as HTMLElement[])[0],
|
||||||
loop: loop && !emoji,
|
loop: loop && !emoji,
|
||||||
autoplay: play,
|
autoplay: play,
|
||||||
animationData: blob,
|
animationData: blob,
|
||||||
|
@ -346,24 +365,25 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
name: 'doc' + doc.id,
|
name: 'doc' + doc.id,
|
||||||
needUpscale,
|
needUpscale,
|
||||||
skipRatio,
|
skipRatio,
|
||||||
toneIndex
|
toneIndex,
|
||||||
}, group, middleware);
|
sync: isCustomEmoji
|
||||||
|
}, group, loadStickerMiddleware ?? middleware);
|
||||||
|
|
||||||
// const deferred = deferredPromise<void>();
|
// const deferred = deferredPromise<void>();
|
||||||
|
|
||||||
const setLockColor = willHaveLock ? () => {
|
const setLockColor = willHaveLock ? () => {
|
||||||
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas);
|
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas[0]);
|
||||||
div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
(div as HTMLElement[]).forEach((div) => div.style.setProperty('--lock-url', `url(${lockUrl})`));
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
animation.addEventListener('firstFrame', () => {
|
const onFirstFrame = (container: HTMLElement, canvas: HTMLCanvasElement) => {
|
||||||
const element = div.firstElementChild;
|
const element = container.firstElementChild;
|
||||||
if(needFadeIn !== false) {
|
if(needFadeIn !== false) {
|
||||||
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
|
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cb = () => {
|
const cb = () => {
|
||||||
if(element && element !== animation.canvas && element.tagName !== 'DIV') {
|
if(element && element !== canvas && element.tagName !== 'DIV') {
|
||||||
element.remove();
|
element.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -374,29 +394,36 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sequentialDom.mutate(() => {
|
sequentialDom.mutate(() => {
|
||||||
animation.canvas.classList.add('fade-in');
|
canvas && canvas.classList.add('fade-in');
|
||||||
if(element) {
|
if(element) {
|
||||||
element.classList.add('fade-out');
|
element.classList.add('fade-out');
|
||||||
}
|
}
|
||||||
|
|
||||||
animation.canvas.addEventListener('animationend', () => {
|
(canvas || element).addEventListener('animationend', () => {
|
||||||
sequentialDom.mutate(() => {
|
sequentialDom.mutate(() => {
|
||||||
animation.canvas.classList.remove('fade-in');
|
canvas && canvas.classList.remove('fade-in');
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
animation.addEventListener('firstFrame', () => {
|
||||||
|
const canvas = animation.canvas[0];
|
||||||
if(withThumb !== false) {
|
if(withThumb !== false) {
|
||||||
saveLottiePreview(doc, animation.canvas, toneIndex);
|
saveLottiePreview(doc, canvas, toneIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(willHaveLock) {
|
if(willHaveLock) {
|
||||||
setLockColor();
|
setLockColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// deferred.resolve();
|
if(!isCustomEmoji) {
|
||||||
|
(div as HTMLElement[]).forEach((container, idx) => {
|
||||||
|
onFirstFrame(container, animation.canvas[idx]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
|
|
||||||
if(emoji) {
|
if(emoji) {
|
||||||
|
@ -409,16 +436,17 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
|
|
||||||
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
||||||
|
|
||||||
attachClickEvent(div, async(e) => {
|
const container = (div as HTMLElement[])[0];
|
||||||
|
attachClickEvent(container, async(e) => {
|
||||||
cancelEvent(e);
|
cancelEvent(e);
|
||||||
const animation = lottieLoader.getAnimation(div);
|
const animation = lottieLoader.getAnimation(container);
|
||||||
|
|
||||||
if(animation.paused) {
|
if(animation.paused) {
|
||||||
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
||||||
if(doc) {
|
if(doc) {
|
||||||
const audio = document.createElement('audio');
|
const audio = document.createElement('audio');
|
||||||
audio.style.display = 'none';
|
audio.style.display = 'none';
|
||||||
div.parentElement.append(audio);
|
container.parentElement.append(audio);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
||||||
|
@ -455,7 +483,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
middleware,
|
middleware,
|
||||||
side: isOut ? 'right' : 'left',
|
side: isOut ? 'right' : 'left',
|
||||||
size: 280,
|
size: 280,
|
||||||
target: div,
|
target: container,
|
||||||
play: true,
|
play: true,
|
||||||
withRandomOffset: true
|
withRandomOffset: true
|
||||||
});
|
});
|
||||||
|
@ -477,7 +505,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
a.t = (a.t - firstTime) / 1000;
|
a.t = (a.t - firstTime) / 1000;
|
||||||
});
|
});
|
||||||
|
|
||||||
const bubble = findUpClassName(div, 'bubble');
|
const bubble = findUpClassName(container, 'bubble');
|
||||||
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
||||||
_: 'sendMessageEmojiInteraction',
|
_: 'sendMessageEmojiInteraction',
|
||||||
msg_id: getServerMessageId(+bubble.dataset.mid),
|
msg_id: getServerMessageId(+bubble.dataset.mid),
|
||||||
|
@ -509,34 +537,29 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
// return deferred;
|
// return deferred;
|
||||||
// await new Promise((resolve) => setTimeout(resolve, 5e3));
|
// await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.timeEnd('render sticker' + doc.id);
|
|
||||||
} else if(asStatic || stickerType === 3) {
|
} else if(asStatic || stickerType === 3) {
|
||||||
let media: HTMLElement;
|
const media: HTMLElement[] = (div as HTMLElement[]).map(() => {
|
||||||
if(asStatic) {
|
let media: HTMLElement;
|
||||||
media = new Image();
|
if(asStatic) {
|
||||||
} else {
|
media = new Image();
|
||||||
media = createVideo();
|
} else {
|
||||||
(media as HTMLVideoElement).muted = true;
|
const video = media = createVideo();
|
||||||
|
video.muted = true;
|
||||||
if(play) {
|
if(play) video.autoplay = true;
|
||||||
(media as HTMLVideoElement).autoplay = true;
|
if(loop) video.loop = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(loop) {
|
media.classList.add('media-sticker');
|
||||||
(media as HTMLVideoElement).loop = true;
|
return media;
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const thumbImage = div.firstElementChild !== media && div.firstElementChild;
|
const thumbImage = (div as HTMLElement[]).map((div, idx) => (div.firstElementChild as HTMLElement) !== media[idx] && div.firstElementChild) as HTMLElement[];
|
||||||
if(needFadeIn !== false) {
|
if(needFadeIn !== false) {
|
||||||
needFadeIn = (needFadeIn || !downloaded || (asStatic ? thumbImage : (!thumbImage || thumbImage.tagName === 'svg'))) && rootScope.settings.animationsEnabled;
|
needFadeIn = (needFadeIn || !downloaded || (asStatic ? thumbImage[0] : (!thumbImage[0] || thumbImage[0].tagName === 'svg'))) && rootScope.settings.animationsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
media.classList.add('media-sticker');
|
|
||||||
|
|
||||||
if(needFadeIn) {
|
if(needFadeIn) {
|
||||||
media.classList.add('fade-in');
|
media.forEach((media) => media.classList.add('fade-in'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<HTMLVideoElement | HTMLImageElement>(async(resolve, reject) => {
|
return new Promise<HTMLVideoElement | HTMLImageElement>(async(resolve, reject) => {
|
||||||
|
@ -546,7 +569,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLoad = () => {
|
const onLoad = (div: HTMLElement, media: HTMLElement, thumbImage: HTMLElement) => {
|
||||||
sequentialDom.mutateElement(div, () => {
|
sequentialDom.mutateElement(div, () => {
|
||||||
div.append(media);
|
div.append(media);
|
||||||
thumbImage && thumbImage.classList.add('fade-out');
|
thumbImage && thumbImage.classList.add('fade-out');
|
||||||
|
@ -579,19 +602,22 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
};
|
};
|
||||||
|
|
||||||
await getCacheContext();
|
await getCacheContext();
|
||||||
if(asStatic) {
|
media.forEach((media, idx) => {
|
||||||
renderImageFromUrl(media, cacheContext.url, onLoad);
|
const cb = () => onLoad((div as HTMLElement[])[idx], media, thumbImage[idx]);
|
||||||
} else {
|
if(asStatic) {
|
||||||
(media as HTMLVideoElement).src = cacheContext.url;
|
renderImageFromUrl(media, cacheContext.url, cb);
|
||||||
onMediaLoad(media as HTMLVideoElement).then(onLoad);
|
} else {
|
||||||
}
|
(media as HTMLVideoElement).src = cacheContext.url;
|
||||||
|
onMediaLoad(media as HTMLVideoElement).then(cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
await getCacheContext();
|
await getCacheContext();
|
||||||
if(cacheContext.url) r();
|
if(cacheContext.url) r();
|
||||||
else {
|
else {
|
||||||
let promise: Promise<any>;
|
let promise: Promise<any>;
|
||||||
if(stickerType === 2 && asStatic) {
|
if(stickerType !== 1 && asStatic) {
|
||||||
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
|
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
|
||||||
// promise = managers.appDocsManager.getThumbURL(doc, thumb).promise
|
// promise = managers.appDocsManager.getThumbURL(doc, thumb).promise
|
||||||
promise = appDownloadManager.downloadMediaURL({media: doc, thumb, queueId: lazyLoadQueue?.queueId});
|
promise = appDownloadManager.downloadMediaURL({media: doc, thumb, queueId: lazyLoadQueue?.queueId});
|
||||||
|
@ -605,8 +631,13 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(exportLoad && (!downloaded || isAnimated)) {
|
||||||
|
ret.load = load;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
const loadPromise: Promise<Awaited<ReturnType<typeof load>> | void> = lazyLoadQueue && (!downloaded || isAnimated) ?
|
const loadPromise: Promise<Awaited<ReturnType<typeof load>> | void> = lazyLoadQueue && (!downloaded || isAnimated) ?
|
||||||
(lazyLoadQueue.push({div, load}), Promise.resolve()) :
|
(lazyLoadQueue.push({div: div[0], load}), Promise.resolve()) :
|
||||||
load();
|
load();
|
||||||
|
|
||||||
if(downloaded && (asStatic/* || stickerType === 3 */)) {
|
if(downloaded && (asStatic/* || stickerType === 3 */)) {
|
||||||
|
@ -618,7 +649,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
|
|
||||||
if(stickerType === 2 && effectThumb && isOut !== undefined && !noPremium) {
|
if(stickerType === 2 && effectThumb && isOut !== undefined && !noPremium) {
|
||||||
attachStickerEffectHandler({
|
attachStickerEffectHandler({
|
||||||
container: div,
|
container: div[0],
|
||||||
doc,
|
doc,
|
||||||
managers,
|
managers,
|
||||||
middleware,
|
middleware,
|
||||||
|
@ -630,7 +661,8 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {render: loadPromise};
|
ret.render = loadPromise as any;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise, relativeEffect, loopEffect}: {
|
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise, relativeEffect, loopEffect}: {
|
||||||
|
|
3
src/environment/customEmojiSupport.ts
Normal file
3
src/environment/customEmojiSupport.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const IS_CUSTOM_EMOJI_SUPPORTED = true;
|
||||||
|
|
||||||
|
export default IS_CUSTOM_EMOJI_SUPPORTED;
|
3
src/environment/imageBitmapSupport.ts
Normal file
3
src/environment/imageBitmapSupport.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const IS_IMAGE_BITMAP_SUPPORTED = typeof(ImageBitmap) !== 'undefined';
|
||||||
|
|
||||||
|
export default IS_IMAGE_BITMAP_SUPPORTED;
|
3
src/environment/webAssemblySupport.ts
Normal file
3
src/environment/webAssemblySupport.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const IS_WEB_ASSEMBLY_SUPPORTED = typeof(WebAssembly) !== 'undefined';
|
||||||
|
|
||||||
|
export default IS_WEB_ASSEMBLY_SUPPORTED;
|
|
@ -19,7 +19,8 @@ type MediaTypeSizes = {
|
||||||
poll: MediaSize,
|
poll: MediaSize,
|
||||||
round: MediaSize,
|
round: MediaSize,
|
||||||
documentName: MediaSize,
|
documentName: MediaSize,
|
||||||
invoice: MediaSize
|
invoice: MediaSize,
|
||||||
|
customEmoji: MediaSize
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MediaSizeType = keyof MediaTypeSizes;
|
export type MediaSizeType = keyof MediaTypeSizes;
|
||||||
|
@ -56,7 +57,8 @@ class MediaSizes extends EventListenerBase<{
|
||||||
poll: makeMediaSize(240, 0),
|
poll: makeMediaSize(240, 0),
|
||||||
round: makeMediaSize(200, 200),
|
round: makeMediaSize(200, 200),
|
||||||
documentName: makeMediaSize(200, 0),
|
documentName: makeMediaSize(200, 0),
|
||||||
invoice: makeMediaSize(240, 240)
|
invoice: makeMediaSize(240, 240),
|
||||||
|
customEmoji: makeMediaSize(18, 18)
|
||||||
},
|
},
|
||||||
desktop: {
|
desktop: {
|
||||||
regular: makeMediaSize(420, 340),
|
regular: makeMediaSize(420, 340),
|
||||||
|
@ -69,7 +71,8 @@ class MediaSizes extends EventListenerBase<{
|
||||||
poll: makeMediaSize(330, 0),
|
poll: makeMediaSize(330, 0),
|
||||||
round: makeMediaSize(280, 280),
|
round: makeMediaSize(280, 280),
|
||||||
documentName: makeMediaSize(240, 0),
|
documentName: makeMediaSize(240, 0),
|
||||||
invoice: makeMediaSize(320, 260)
|
invoice: makeMediaSize(320, 260),
|
||||||
|
customEmoji: makeMediaSize(18, 18)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default function preloadAnimatedEmojiSticker(emoji: string, width?: numbe
|
||||||
}, 'none');
|
}, 'none');
|
||||||
|
|
||||||
animation.addEventListener('firstFrame', () => {
|
animation.addEventListener('firstFrame', () => {
|
||||||
saveLottiePreview(doc, animation.canvas, toneIndex);
|
saveLottiePreview(doc, animation.canvas[0], toneIndex);
|
||||||
animation.remove();
|
animation.remove();
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,7 +49,7 @@ class SequentialDom {
|
||||||
const promise = isConnected ? this.mutate() : Promise.resolve();
|
const promise = isConnected ? this.mutate() : Promise.resolve();
|
||||||
|
|
||||||
if(callback !== undefined) {
|
if(callback !== undefined) {
|
||||||
if(isConnected) {
|
if(!isConnected) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
promise.then(() => callback());
|
promise.then(() => callback());
|
||||||
|
|
|
@ -234,7 +234,7 @@ export class AppDialogsManager {
|
||||||
private managers: AppManagers;
|
private managers: AppManagers;
|
||||||
private selectTab: ReturnType<typeof horizontalMenu>;
|
private selectTab: ReturnType<typeof horizontalMenu>;
|
||||||
|
|
||||||
constructor() {
|
public start() {
|
||||||
const managers = this.managers = getProxiedManagers();
|
const managers = this.managers = getProxiedManagers();
|
||||||
|
|
||||||
this.contextMenu = new DialogsContextMenu(managers);
|
this.contextMenu = new DialogsContextMenu(managers);
|
||||||
|
|
|
@ -134,6 +134,7 @@ export class AppDocsManager extends AppManager {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'documentAttributeCustomEmoji':
|
||||||
case 'documentAttributeSticker':
|
case 'documentAttributeSticker':
|
||||||
if(attribute.alt !== undefined) {
|
if(attribute.alt !== undefined) {
|
||||||
doc.stickerEmojiRaw = attribute.alt;
|
doc.stickerEmojiRaw = attribute.alt;
|
||||||
|
@ -153,7 +154,7 @@ export class AppDocsManager extends AppManager {
|
||||||
doc.sticker = 1;
|
doc.sticker = 1;
|
||||||
} else if(doc.mime_type === 'video/webm') {
|
} else if(doc.mime_type === 'video/webm') {
|
||||||
if(!getEnvironment().IS_WEBM_SUPPORTED) {
|
if(!getEnvironment().IS_WEBM_SUPPORTED) {
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.type = 'sticker';
|
doc.type = 'sticker';
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {MyDocument} from './appDocsManager';
|
||||||
import App from '../../config/app';
|
import App from '../../config/app';
|
||||||
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
||||||
import isObject from '../../helpers/object/isObject';
|
import isObject from '../../helpers/object/isObject';
|
||||||
|
@ -13,6 +14,8 @@ import fixEmoji from '../richTextProcessor/fixEmoji';
|
||||||
import SearchIndex from '../searchIndex';
|
import SearchIndex from '../searchIndex';
|
||||||
import stateStorage from '../stateStorage';
|
import stateStorage from '../stateStorage';
|
||||||
import {AppManager} from './manager';
|
import {AppManager} from './manager';
|
||||||
|
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
|
||||||
|
import pause from '../../helpers/schedulers/pause';
|
||||||
|
|
||||||
type EmojiLangPack = {
|
type EmojiLangPack = {
|
||||||
keywords: {
|
keywords: {
|
||||||
|
@ -44,6 +47,9 @@ export class AppEmojiManager extends AppManager {
|
||||||
private recent: string[];
|
private recent: string[];
|
||||||
private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>;
|
private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>;
|
||||||
|
|
||||||
|
private getCustomEmojiDocumentsPromise: Promise<any>;
|
||||||
|
private getCustomEmojiDocumentPromises: Map<DocId, CancellablePromise<MyDocument>> = new Map();
|
||||||
|
|
||||||
/* public getPopularEmoji() {
|
/* public getPopularEmoji() {
|
||||||
return stateStorage.get('emojis_popular').then((popEmojis) => {
|
return stateStorage.get('emojis_popular').then((popEmojis) => {
|
||||||
var result = []
|
var result = []
|
||||||
|
@ -230,4 +236,61 @@ export class AppEmojiManager extends AppManager {
|
||||||
this.rootScope.dispatchEvent('emoji_recent', emoji);
|
this.rootScope.dispatchEvent('emoji_recent', emoji);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCustomEmojiDocuments(docIds: DocId[]) {
|
||||||
|
return this.apiManager.invokeApi('messages.getCustomEmojiDocuments', {document_id: docIds}).then((documents) => {
|
||||||
|
return documents.map((doc) => {
|
||||||
|
return this.appDocsManager.saveDoc(doc, {
|
||||||
|
type: 'customEmoji',
|
||||||
|
docId: doc.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCachedCustomEmojiDocuments(docIds: DocId[]) {
|
||||||
|
return docIds.map((docId) => this.appDocsManager.getDoc(docId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDebouncedGetCustomEmojiDocuments() {
|
||||||
|
if(this.getCustomEmojiDocumentsPromise || !this.getCustomEmojiDocumentPromises.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCustomEmojiDocumentsPromise = pause(0).then(() => {
|
||||||
|
const allIds = [...this.getCustomEmojiDocumentPromises.keys()];
|
||||||
|
do {
|
||||||
|
const ids = allIds.splice(0, 100);
|
||||||
|
this.getCustomEmojiDocuments(ids).then((docs) => {
|
||||||
|
docs.forEach((doc, idx) => {
|
||||||
|
const docId = ids[idx];
|
||||||
|
const deferred = this.getCustomEmojiDocumentPromises.get(docId);
|
||||||
|
this.getCustomEmojiDocumentPromises.delete(docId);
|
||||||
|
deferred.resolve(doc);
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
this.setDebouncedGetCustomEmojiDocuments();
|
||||||
|
});
|
||||||
|
} while(allIds.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCustomEmojiDocument(id: DocId) {
|
||||||
|
let promise = this.getCustomEmojiDocumentPromises.get(id);
|
||||||
|
if(promise) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = this.appDocsManager.getDoc(id);
|
||||||
|
if(doc) {
|
||||||
|
return Promise.resolve(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = deferredPromise();
|
||||||
|
this.getCustomEmojiDocumentPromises.set(id, promise);
|
||||||
|
|
||||||
|
this.setDebouncedGetCustomEmojiDocuments();
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import appNavigationController from '../../components/appNavigationController';
|
||||||
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
|
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
|
||||||
import I18n, {i18n, join, LangPackKey} from '../langPack';
|
import I18n, {i18n, join, LangPackKey} from '../langPack';
|
||||||
import {ChatFull, ChatInvite, ChatParticipant, ChatParticipants, Message, MessageAction, MessageMedia, SendMessageAction} from '../../layer';
|
import {ChatFull, ChatInvite, ChatParticipant, ChatParticipants, Message, MessageAction, MessageMedia, SendMessageAction} from '../../layer';
|
||||||
import {hslaStringToHex} from '../../helpers/color';
|
|
||||||
import PeerTitle from '../../components/peerTitle';
|
import PeerTitle from '../../components/peerTitle';
|
||||||
import PopupPeer from '../../components/popups/peer';
|
import PopupPeer from '../../components/popups/peer';
|
||||||
import blurActiveElement from '../../helpers/dom/blurActiveElement';
|
import blurActiveElement from '../../helpers/dom/blurActiveElement';
|
||||||
|
@ -92,15 +91,6 @@ import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount
|
||||||
import findUpClassName from '../../helpers/dom/findUpClassName';
|
import findUpClassName from '../../helpers/dom/findUpClassName';
|
||||||
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
||||||
import PopupPayment from '../../components/popups/payment';
|
import PopupPayment from '../../components/popups/payment';
|
||||||
import {getMiddleware} from '../../helpers/middleware';
|
|
||||||
import {wrapSticker} from '../../components/wrappers';
|
|
||||||
import windowSize from '../../helpers/windowSize';
|
|
||||||
import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb';
|
|
||||||
import {makeMediaSize} from '../../helpers/mediaSize';
|
|
||||||
import RLottiePlayer from '../rlottie/rlottiePlayer';
|
|
||||||
import type {MyDocument} from './appDocsManager';
|
|
||||||
import deferredPromise from '../../helpers/cancellablePromise';
|
|
||||||
import {STICKER_EFFECT_MULTIPLIER} from '../../components/wrappers/sticker';
|
|
||||||
|
|
||||||
export const CHAT_ANIMATION_GROUP: AnimationItemGroup = 'chat';
|
export const CHAT_ANIMATION_GROUP: AnimationItemGroup = 'chat';
|
||||||
|
|
||||||
|
@ -380,7 +370,7 @@ export class AppImManager extends EventListenerBase<{
|
||||||
|
|
||||||
(window as any).onSpoilerClick = (e: MouseEvent) => {
|
(window as any).onSpoilerClick = (e: MouseEvent) => {
|
||||||
const spoiler = findUpClassName(e.target, 'spoiler');
|
const spoiler = findUpClassName(e.target, 'spoiler');
|
||||||
const parentElement = findUpClassName(spoiler, 'message') || spoiler.parentElement;
|
const parentElement = findUpClassName(spoiler, 'spoilers-container') || spoiler.parentElement;
|
||||||
|
|
||||||
const className = 'is-spoiler-visible';
|
const className = 'is-spoiler-visible';
|
||||||
const isVisible = parentElement.classList.contains(className);
|
const isVisible = parentElement.classList.contains(className);
|
||||||
|
|
|
@ -2789,9 +2789,9 @@ export class AppMessagesManager extends AppManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isMessage && !unsupported && message.entities) {
|
// if(isMessage && !unsupported && message.entities) {
|
||||||
unsupported = message.entities.some((entity) => entity._ === 'messageEntityCustomEmoji');
|
// unsupported = message.entities.some((entity) => entity._ === 'messageEntityCustomEmoji');
|
||||||
}
|
// }
|
||||||
|
|
||||||
if(isMessage && unsupported) {
|
if(isMessage && unsupported) {
|
||||||
message.media = {_: 'messageMediaUnsupported'};
|
message.media = {_: 'messageMediaUnsupported'};
|
||||||
|
|
|
@ -11,7 +11,7 @@ import deepEqual from '../../helpers/object/deepEqual';
|
||||||
import {AppManager} from '../appManagers/manager';
|
import {AppManager} from '../appManagers/manager';
|
||||||
import makeError from '../../helpers/makeError';
|
import makeError from '../../helpers/makeError';
|
||||||
|
|
||||||
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds | ReferenceContext.referenceContextReactions | ReferenceContext.referenceContextUserFull;
|
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds | ReferenceContext.referenceContextReactions | ReferenceContext.referenceContextUserFull | ReferenceContext.referenceContextCustomEmoji;
|
||||||
export namespace ReferenceContext {
|
export namespace ReferenceContext {
|
||||||
export type referenceContextProfilePhoto = {
|
export type referenceContextProfilePhoto = {
|
||||||
type: 'profilePhoto',
|
type: 'profilePhoto',
|
||||||
|
@ -36,6 +36,11 @@ export namespace ReferenceContext {
|
||||||
type: 'userFull',
|
type: 'userFull',
|
||||||
userId: UserId
|
userId: UserId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type referenceContextCustomEmoji = {
|
||||||
|
type: 'customEmoji',
|
||||||
|
docId: DocId
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReferenceBytes = Photo.photo['file_reference'];
|
export type ReferenceBytes = Photo.photo['file_reference'];
|
||||||
|
@ -150,6 +155,10 @@ export class ReferenceDatabase extends AppManager {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'customEmoji': {
|
||||||
|
promise = this.appEmojiManager.getCustomEmojiDocuments([context.docId]);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
this.log.warn('refreshReference: not implemented context', context);
|
this.log.warn('refreshReference: not implemented context', context);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
|
|
|
@ -79,7 +79,6 @@ export default function parseEntities(text: string) {
|
||||||
length: 1
|
length: 1
|
||||||
});
|
});
|
||||||
} else if(match[8]) { // Emoji
|
} else if(match[8]) { // Emoji
|
||||||
// console.log('hit', match[8]);
|
|
||||||
const unified = getEmojiUnified(match[8]);
|
const unified = getEmojiUnified(match[8]);
|
||||||
if(unified) {
|
if(unified) {
|
||||||
entities.push({
|
entities.push({
|
||||||
|
|
|
@ -19,6 +19,237 @@ import setBlankToAnchor from './setBlankToAnchor';
|
||||||
import wrapUrl from './wrapUrl';
|
import wrapUrl from './wrapUrl';
|
||||||
import EMOJI_VERSIONS_SUPPORTED from '../../environment/emojiVersionsSupport';
|
import EMOJI_VERSIONS_SUPPORTED from '../../environment/emojiVersionsSupport';
|
||||||
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
||||||
|
import IS_CUSTOM_EMOJI_SUPPORTED from '../../environment/customEmojiSupport';
|
||||||
|
import rootScope from '../rootScope';
|
||||||
|
import mediaSizes from '../../helpers/mediaSizes';
|
||||||
|
import {wrapSticker} from '../../components/wrappers';
|
||||||
|
import RLottiePlayer from '../rlottie/rlottiePlayer';
|
||||||
|
import animationIntersector from '../../components/animationIntersector';
|
||||||
|
import type {MyDocument} from '../appManagers/appDocsManager';
|
||||||
|
import LazyLoadQueue from '../../components/lazyLoadQueue';
|
||||||
|
import {Awaited} from '../../types';
|
||||||
|
import sequentialDom from '../../helpers/sequentialDom';
|
||||||
|
import {MediaSize} from '../../helpers/mediaSize';
|
||||||
|
import IS_WEBM_SUPPORTED from '../../environment/webmSupport';
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for(const entry of entries) {
|
||||||
|
const renderer = entry.target.parentElement as CustomEmojiRendererElement;
|
||||||
|
renderer.setDimensionsFromRect(entry.contentRect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class CustomEmojiElement extends HTMLElement {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomEmojiRendererElement extends HTMLElement {
|
||||||
|
public canvas: HTMLCanvasElement;
|
||||||
|
public context: CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
public players: Map<CustomEmojiElement[], RLottiePlayer>;
|
||||||
|
public clearedContainers: Set<CustomEmojiElement[]>;
|
||||||
|
|
||||||
|
public paused: boolean;
|
||||||
|
public autoplay: boolean;
|
||||||
|
|
||||||
|
public middleware: () => boolean;
|
||||||
|
public keys: string[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.classList.add('custom-emoji-renderer');
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
this.canvas.classList.add('custom-emoji-canvas');
|
||||||
|
this.context = this.canvas.getContext('2d');
|
||||||
|
this.append(this.canvas);
|
||||||
|
|
||||||
|
this.paused = false;
|
||||||
|
this.autoplay = true;
|
||||||
|
this.players = new Map();
|
||||||
|
this.clearedContainers = new Set();
|
||||||
|
this.keys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
// this.setDimensions();
|
||||||
|
animationIntersector.addAnimation(this, 'EMOJI');
|
||||||
|
resizeObserver.observe(this.canvas);
|
||||||
|
|
||||||
|
this.connectedCallback = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
for(const key of this.keys) {
|
||||||
|
const l = lotties.get(key);
|
||||||
|
if(!l) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!--l.counter) {
|
||||||
|
if(l.player instanceof RLottiePlayer) {
|
||||||
|
l.player.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
lotties.delete(key);
|
||||||
|
|
||||||
|
if(!lotties.size) {
|
||||||
|
clearRenderInterval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeObserver.unobserve(this.canvas);
|
||||||
|
|
||||||
|
this.disconnectedCallback = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOffsets(offsetsMap: Map<CustomEmojiElement[], {top: number, left: number}[]> = new Map()) {
|
||||||
|
for(const [containers, player] of this.players) {
|
||||||
|
const offsets = containers.map((container) => {
|
||||||
|
return {
|
||||||
|
top: container.offsetTop,
|
||||||
|
left: container.offsetLeft
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
offsetsMap.set(containers, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offsetsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearCanvas() {
|
||||||
|
const {context, canvas} = this;
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(offsetsMap: ReturnType<CustomEmojiRendererElement['getOffsets']>) {
|
||||||
|
const {context, canvas} = this;
|
||||||
|
const {width, height, dpr} = canvas;
|
||||||
|
for(const [containers, player] of this.players) {
|
||||||
|
const frame = topFrames.get(player);
|
||||||
|
if(!frame) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isImageData = frame instanceof ImageData;
|
||||||
|
const {width: stickerWidth, height: stickerHeight} = player.canvas[0];
|
||||||
|
const offsets = offsetsMap.get(containers);
|
||||||
|
const maxTop = height - stickerHeight;
|
||||||
|
const maxLeft = width - stickerWidth;
|
||||||
|
|
||||||
|
if(!this.clearedContainers.has(containers)) {
|
||||||
|
containers.forEach((container) => {
|
||||||
|
container.textContent = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
this.clearedContainers.add(containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
offsets.forEach(({top, left}) => {
|
||||||
|
top = Math.round(top * dpr), left = Math.round(left * dpr);
|
||||||
|
if(/* top > maxTop || */left > maxLeft) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isImageData) {
|
||||||
|
context.putImageData(frame as ImageData, left, top);
|
||||||
|
} else {
|
||||||
|
// context.clearRect(left, top, width, height);
|
||||||
|
context.drawImage(frame as ImageBitmap, left, top, stickerWidth, stickerHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkForAnyFrame() {
|
||||||
|
for(const [containers, player] of this.players) {
|
||||||
|
if(topFrames.has(player)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause() {
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public play() {
|
||||||
|
this.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove() {
|
||||||
|
this.canvas.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDimensions() {
|
||||||
|
const {canvas} = this;
|
||||||
|
sequentialDom.mutateElement(canvas, () => {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
this.setDimensionsFromRect(rect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDimensionsFromRect(rect: DOMRect) {
|
||||||
|
const {canvas} = this;
|
||||||
|
const dpr = canvas.dpr ??= Math.min(2, window.devicePixelRatio);
|
||||||
|
canvas.width = Math.round(rect.width * dpr);
|
||||||
|
canvas.height = Math.round(rect.height * dpr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type R = CustomEmojiRendererElement;
|
||||||
|
|
||||||
|
let renderInterval: number;
|
||||||
|
const top: Array<R> = [];
|
||||||
|
const topFrames: Map<RLottiePlayer, Parameters<RLottiePlayer['overrideRender']>[0]> = new Map();
|
||||||
|
const lotties: Map<string, {player: Promise<RLottiePlayer> | RLottiePlayer, middlewares: Set<() => boolean>, counter: number}> = new Map();
|
||||||
|
const rerere = () => {
|
||||||
|
const t = top.filter((r) => !r.paused && r.isConnected && r.checkForAnyFrame());
|
||||||
|
if(!t.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetsMap: Map<CustomEmojiElement[], {top: number, left: number}[]> = new Map();
|
||||||
|
for(const r of t) {
|
||||||
|
r.getOffsets(offsetsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const r of t) {
|
||||||
|
r.clearCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const r of t) {
|
||||||
|
r.render(offsetsMap);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const CUSTOM_EMOJI_FPS = 60;
|
||||||
|
const CUSTOM_EMOJI_FRAME_INTERVAL = 1000 / CUSTOM_EMOJI_FPS;
|
||||||
|
const setRenderInterval = () => {
|
||||||
|
if(renderInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInterval = window.setInterval(rerere, CUSTOM_EMOJI_FRAME_INTERVAL);
|
||||||
|
rerere();
|
||||||
|
};
|
||||||
|
const clearRenderInterval = () => {
|
||||||
|
if(!renderInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(renderInterval);
|
||||||
|
renderInterval = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
(window as any).lotties = lotties;
|
||||||
|
|
||||||
|
customElements.define('custom-emoji-element', CustomEmojiElement);
|
||||||
|
customElements.define('custom-emoji-renderer-element', CustomEmojiRendererElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * Expecting correctly sorted nested entities (RichTextProcessor.sortEntities)
|
* * Expecting correctly sorted nested entities (RichTextProcessor.sortEntities)
|
||||||
|
@ -46,7 +277,13 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
text: string,
|
text: string,
|
||||||
lastEntity?: MessageEntity
|
lastEntity?: MessageEntity
|
||||||
},
|
},
|
||||||
voodoo?: boolean
|
voodoo?: boolean,
|
||||||
|
customEmojis?: {[docId: DocId]: CustomEmojiElement[]},
|
||||||
|
loadPromises?: Promise<any>[],
|
||||||
|
middleware?: () => boolean,
|
||||||
|
wrappingSpoiler?: boolean,
|
||||||
|
lazyLoadQueue?: LazyLoadQueue,
|
||||||
|
customEmojiSize?: MediaSize
|
||||||
}> = {}) {
|
}> = {}) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
if(!text) {
|
if(!text) {
|
||||||
|
@ -59,6 +296,8 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
text
|
text
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const customEmojis = options.customEmojis ??= {};
|
||||||
|
|
||||||
const entities = options.entities ??= parseEntities(nasty.text);
|
const entities = options.entities ??= parseEntities(nasty.text);
|
||||||
|
|
||||||
const passEntities = options.passEntities ??= {};
|
const passEntities = options.passEntities ??= {};
|
||||||
|
@ -217,6 +456,25 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'messageEntityCustomEmoji': {
|
||||||
|
if(!IS_CUSTOM_EMOJI_SUPPORTED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nextEntity?._ === 'messageEntityEmoji') {
|
||||||
|
++nasty.i;
|
||||||
|
nasty.lastEntity = nextEntity;
|
||||||
|
nasty.usedLength += nextEntity.length;
|
||||||
|
nextEntity = entities[nasty.i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
(customEmojis[entity.document_id] ??= []).push(element = new CustomEmojiElement());
|
||||||
|
element.classList.add('custom-emoji');
|
||||||
|
|
||||||
|
property = 'alt';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'messageEntityEmoji': {
|
case 'messageEntityEmoji': {
|
||||||
let isSupported = IS_EMOJI_SUPPORTED;
|
let isSupported = IS_EMOJI_SUPPORTED;
|
||||||
if(isSupported) {
|
if(isSupported) {
|
||||||
|
@ -287,7 +545,8 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
if(nextEntity?._ === 'messageEntityUrl' &&
|
if(nextEntity?._ === 'messageEntityUrl' &&
|
||||||
nextEntity.length === entity.length &&
|
nextEntity.length === entity.length &&
|
||||||
nextEntity.offset === entity.offset) {
|
nextEntity.offset === entity.offset) {
|
||||||
nasty.i++;
|
nasty.lastEntity = nextEntity;
|
||||||
|
++nasty.i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(url !== fullEntityText) {
|
if(url !== fullEntityText) {
|
||||||
|
@ -389,6 +648,14 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
const encoded = encodeSpoiler(nasty.text, entity);
|
const encoded = encodeSpoiler(nasty.text, entity);
|
||||||
nasty.text = encoded.text;
|
nasty.text = encoded.text;
|
||||||
partText = encoded.entityText;
|
partText = encoded.entityText;
|
||||||
|
nasty.usedLength += partText.length;
|
||||||
|
let n: MessageEntity;
|
||||||
|
for(; n = entities[nasty.i + 1], n && n.offset < endOffset;) {
|
||||||
|
// nasty.usedLength += n.length;
|
||||||
|
++nasty.i;
|
||||||
|
nasty.lastEntity = n;
|
||||||
|
nextEntity = entities[nasty.i + 1];
|
||||||
|
}
|
||||||
} else if(options.wrappingDraft) {
|
} else if(options.wrappingDraft) {
|
||||||
element = document.createElement('span');
|
element = document.createElement('span');
|
||||||
element.style.fontFamily = 'spoiler';
|
element.style.fontFamily = 'spoiler';
|
||||||
|
@ -464,6 +731,159 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||||
(lastElement || fragment).append(nasty.text.slice(nasty.usedLength));
|
(lastElement || fragment).append(nasty.text.slice(nasty.usedLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const docIds = Object.keys(customEmojis) as DocId[];
|
||||||
|
if(docIds.length) {
|
||||||
|
const managers = rootScope.managers;
|
||||||
|
const middleware = options.middleware;
|
||||||
|
const renderer = new CustomEmojiRendererElement();
|
||||||
|
renderer.middleware = middleware;
|
||||||
|
top.push(renderer);
|
||||||
|
fragment.prepend(renderer);
|
||||||
|
|
||||||
|
const size = options.customEmojiSize || mediaSizes.active.customEmoji;
|
||||||
|
const loadPromise = managers.appEmojiManager.getCachedCustomEmojiDocuments(docIds).then((docs) => {
|
||||||
|
console.log(docs);
|
||||||
|
if(middleware && !middleware()) return;
|
||||||
|
|
||||||
|
const loadPromises: Promise<any>[] = [];
|
||||||
|
const wrap = (doc: MyDocument, _loadPromises?: Promise<any>[]): Promise<Awaited<ReturnType<typeof wrapSticker>> & {onRender?: () => void}> => {
|
||||||
|
const containers = customEmojis[doc.id];
|
||||||
|
const isLottie = doc.sticker === 2;
|
||||||
|
|
||||||
|
const loadPromises: Promise<any>[] = [];
|
||||||
|
const promise = wrapSticker({
|
||||||
|
div: containers,
|
||||||
|
doc,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
loop: true,
|
||||||
|
play: true,
|
||||||
|
managers,
|
||||||
|
isCustomEmoji: true,
|
||||||
|
group: 'none',
|
||||||
|
loadPromises,
|
||||||
|
middleware,
|
||||||
|
exportLoad: true,
|
||||||
|
needFadeIn: false,
|
||||||
|
loadStickerMiddleware: isLottie && middleware ? () => {
|
||||||
|
if(lotties.get(key) !== l) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let good = !l.middlewares.size;
|
||||||
|
for(const middleware of l.middlewares) {
|
||||||
|
if(middleware()) {
|
||||||
|
good = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return good;
|
||||||
|
} : undefined,
|
||||||
|
static: doc.mime_type === 'video/webm' && !IS_WEBM_SUPPORTED
|
||||||
|
});
|
||||||
|
|
||||||
|
if(_loadPromises) {
|
||||||
|
promise.then(() => _loadPromises.push(...loadPromises));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isLottie) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRender = (player: Awaited<Awaited<typeof promise>['render']>) => Promise.all(loadPromises).then(() => {
|
||||||
|
if(player instanceof RLottiePlayer && (!middleware || middleware())) {
|
||||||
|
l.player = player;
|
||||||
|
|
||||||
|
const playerCanvas = player.canvas[0];
|
||||||
|
renderer.canvas.dpr = playerCanvas.dpr;
|
||||||
|
renderer.players.set(containers, player);
|
||||||
|
|
||||||
|
setRenderInterval();
|
||||||
|
|
||||||
|
player.overrideRender ??= (frame) => {
|
||||||
|
topFrames.set(player, frame);
|
||||||
|
// frames.set(containers, frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
l.middlewares.delete(middleware);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = [doc.id, size.width, size.height].join('-');
|
||||||
|
renderer.keys.push(key);
|
||||||
|
let l = lotties.get(key);
|
||||||
|
if(!l) {
|
||||||
|
l = {
|
||||||
|
player: undefined,
|
||||||
|
middlewares: new Set(),
|
||||||
|
counter: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
lotties.set(key, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
++l.counter;
|
||||||
|
|
||||||
|
if(middleware) {
|
||||||
|
l.middlewares.add(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then((res) => ({...res, onRender}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const missing: DocId[] = [];
|
||||||
|
const cachedPromises = docs.map((doc, idx) => {
|
||||||
|
if(!doc) {
|
||||||
|
missing.push(docIds[idx]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrap(doc, loadPromises);
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
const uncachedPromisesPromise = managers.appEmojiManager.getCustomEmojiDocuments(missing).then((docs) => {
|
||||||
|
if(middleware && !middleware()) return [];
|
||||||
|
return docs.filter(Boolean).map((doc) => wrap(doc));
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadFromPromises = (promises: typeof cachedPromises) => {
|
||||||
|
return Promise.all(promises).then((arr) => {
|
||||||
|
const promises = arr.map(({load, onRender}) => {
|
||||||
|
if(!load) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return load().then(onRender);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
if(middleware && !middleware()) return;
|
||||||
|
const cached = loadFromPromises(cachedPromises);
|
||||||
|
const uncached = uncachedPromisesPromise.then((promises) => loadFromPromises(promises));
|
||||||
|
return Promise.all([cached, uncached]);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(options.lazyLoadQueue) {
|
||||||
|
options.lazyLoadQueue.push({
|
||||||
|
div: renderer.canvas,
|
||||||
|
load
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(cachedPromises).then(() => Promise.all(loadPromises)).then(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
// recordPromise(loadPromise, 'render emojis: ' + docIds.length);
|
||||||
|
|
||||||
|
options.loadPromises?.push(loadPromise);
|
||||||
|
}
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,9 @@ import {logger, LogTypes} from '../logger';
|
||||||
import RLottiePlayer, {RLottieOptions} from './rlottiePlayer';
|
import RLottiePlayer, {RLottieOptions} from './rlottiePlayer';
|
||||||
import QueryableWorker from './queryableWorker';
|
import QueryableWorker from './queryableWorker';
|
||||||
import blobConstruct from '../../helpers/blob/blobConstruct';
|
import blobConstruct from '../../helpers/blob/blobConstruct';
|
||||||
import rootScope from '../rootScope';
|
|
||||||
import apiManagerProxy from '../mtproto/mtprotoworker';
|
import apiManagerProxy from '../mtproto/mtprotoworker';
|
||||||
|
import IS_WEB_ASSEMBLY_SUPPORTED from '../../environment/webAssemblySupport';
|
||||||
|
import makeError from '../../helpers/makeError';
|
||||||
|
|
||||||
export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' |
|
export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' |
|
||||||
'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' |
|
'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' |
|
||||||
|
@ -21,12 +22,12 @@ export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' |
|
||||||
'voice_outlined2' | 'voip_filled' | 'voice_mini';
|
'voice_outlined2' | 'voip_filled' | 'voice_mini';
|
||||||
|
|
||||||
export class LottieLoader {
|
export class LottieLoader {
|
||||||
private isWebAssemblySupported = typeof(WebAssembly) !== 'undefined';
|
private loadPromise: Promise<void> = !IS_WEB_ASSEMBLY_SUPPORTED ? Promise.reject() : undefined;
|
||||||
private loadPromise: Promise<void> = !this.isWebAssemblySupported ? Promise.reject() : undefined;
|
|
||||||
private loaded = false;
|
private loaded = false;
|
||||||
|
|
||||||
private workersLimit = 4;
|
private workersLimit = 4;
|
||||||
private players: {[reqId: number]: RLottiePlayer} = {};
|
private players: {[reqId: number]: RLottiePlayer} = {};
|
||||||
|
private playersByCacheName: {[cacheName: string]: Set<RLottiePlayer>} = {};
|
||||||
|
|
||||||
private workers: QueryableWorker[] = [];
|
private workers: QueryableWorker[] = [];
|
||||||
private curWorkerNum = 0;
|
private curWorkerNum = 0;
|
||||||
|
@ -35,7 +36,7 @@ export class LottieLoader {
|
||||||
|
|
||||||
public getAnimation(element: HTMLElement) {
|
public getAnimation(element: HTMLElement) {
|
||||||
for(const i in this.players) {
|
for(const i in this.players) {
|
||||||
if(this.players[i].el === element) {
|
if(this.players[i].el.includes(element)) {
|
||||||
return this.players[i];
|
return this.players[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ export class LottieLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadAnimationFromURL(params: Omit<RLottieOptions, 'animationData'>, url: string): Promise<RLottiePlayer> {
|
public loadAnimationFromURL(params: Omit<RLottieOptions, 'animationData'>, url: string): Promise<RLottiePlayer> {
|
||||||
if(!this.isWebAssemblySupported) {
|
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||||
return this.loadPromise as any;
|
return this.loadPromise as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,22 +137,30 @@ export class LottieLoader {
|
||||||
group: AnimationItemGroup = params.group || '',
|
group: AnimationItemGroup = params.group || '',
|
||||||
middleware?: () => boolean
|
middleware?: () => boolean
|
||||||
): Promise<RLottiePlayer> {
|
): Promise<RLottiePlayer> {
|
||||||
if(!this.isWebAssemblySupported) {
|
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||||
return this.loadPromise as any;
|
return this.loadPromise as any;
|
||||||
}
|
}
|
||||||
// params.autoplay = true;
|
|
||||||
|
|
||||||
if(!this.loaded) {
|
if(!this.loaded) {
|
||||||
await this.loadLottieWorkers();
|
await this.loadLottieWorkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(middleware && !middleware()) {
|
if(middleware && !middleware()) {
|
||||||
throw new Error('middleware');
|
throw makeError('MIDDLEWARE');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(params.sync) {
|
||||||
|
const cacheName = RLottiePlayer.CACHE.generateName(params.name, params.width, params.height, params.color, params.toneIndex);
|
||||||
|
const players = this.playersByCacheName[cacheName];
|
||||||
|
if(players?.size) {
|
||||||
|
return Promise.resolve(players.entries().next().value[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const containers = Array.isArray(params.container) ? params.container : [params.container];
|
||||||
if(!params.width || !params.height) {
|
if(!params.width || !params.height) {
|
||||||
params.width = parseInt(params.container.style.width);
|
params.width = parseInt(containers[0].style.width);
|
||||||
params.height = parseInt(params.container.style.height);
|
params.height = parseInt(containers[0].style.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!params.width || !params.height) {
|
if(!params.width || !params.height) {
|
||||||
|
@ -160,7 +169,7 @@ export class LottieLoader {
|
||||||
|
|
||||||
params.group = group;
|
params.group = group;
|
||||||
|
|
||||||
const player = this.initPlayer(params.container, params);
|
const player = this.initPlayer(containers, params);
|
||||||
|
|
||||||
if(group !== 'none') {
|
if(group !== 'none') {
|
||||||
animationIntersector.addAnimation(player, group);
|
animationIntersector.addAnimation(player, group);
|
||||||
|
@ -170,42 +179,41 @@ export class LottieLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPlayerLoaded = (reqId: number, frameCount: number, fps: number) => {
|
private onPlayerLoaded = (reqId: number, frameCount: number, fps: number) => {
|
||||||
const rlPlayer = this.players[reqId];
|
const player = this.players[reqId];
|
||||||
if(!rlPlayer) {
|
if(!player) {
|
||||||
this.log.warn('onPlayerLoaded on destroyed player:', reqId, frameCount);
|
this.log.warn('onPlayerLoaded on destroyed player:', reqId, frameCount);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log.debug('onPlayerLoaded');
|
this.log.debug('onPlayerLoaded');
|
||||||
rlPlayer.onLoad(frameCount, fps);
|
player.onLoad(frameCount, fps);
|
||||||
// rlPlayer.addListener('firstFrame', () => {
|
|
||||||
// animationIntersector.addAnimation(player, group);
|
|
||||||
// }, true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onFrame = (reqId: number, frameNo: number, frame: Uint8ClampedArray) => {
|
private onFrame = (reqId: number, frameNo: number, frame: Uint8ClampedArray | ImageBitmap) => {
|
||||||
const rlPlayer = this.players[reqId];
|
const player = this.players[reqId];
|
||||||
if(!rlPlayer) {
|
if(!player) {
|
||||||
this.log.warn('onFrame on destroyed player:', reqId, frameNo);
|
this.log.warn('onFrame on destroyed player:', reqId, frameNo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rlPlayer.clamped !== undefined) {
|
if(player.clamped !== undefined && frame instanceof Uint8ClampedArray) {
|
||||||
rlPlayer.clamped = frame;
|
player.clamped = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
rlPlayer.renderFrame(frame, frameNo);
|
player.renderFrame(frame, frameNo);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPlayerError = (reqId: number, error: Error) => {
|
private onPlayerError = (reqId: number, error: Error) => {
|
||||||
const rlPlayer = this.players[reqId];
|
const player = this.players[reqId];
|
||||||
if(rlPlayer) {
|
if(!player) {
|
||||||
// ! will need refactoring later, this is not the best way to remove the animation
|
return;
|
||||||
const animations = animationIntersector.getAnimations(rlPlayer.el);
|
|
||||||
animations.forEach((animation) => {
|
|
||||||
animationIntersector.checkAnimation(animation, true, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! will need refactoring later, this is not the best way to remove the animation
|
||||||
|
const animations = animationIntersector.getAnimations(player.el[0]);
|
||||||
|
animations.forEach((animation) => {
|
||||||
|
animationIntersector.checkAnimation(animation, true, true);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDestroy(reqId: number) {
|
public onDestroy(reqId: number) {
|
||||||
|
@ -213,6 +221,10 @@ export class LottieLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroyWorkers() {
|
public destroyWorkers() {
|
||||||
|
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.workers.forEach((worker, idx) => {
|
this.workers.forEach((worker, idx) => {
|
||||||
worker.terminate();
|
worker.terminate();
|
||||||
this.log('worker #' + idx + ' terminated');
|
this.log('worker #' + idx + ' terminated');
|
||||||
|
@ -220,23 +232,40 @@ export class LottieLoader {
|
||||||
|
|
||||||
this.log('workers destroyed');
|
this.log('workers destroyed');
|
||||||
this.workers.length = 0;
|
this.workers.length = 0;
|
||||||
|
this.curWorkerNum = 0;
|
||||||
|
this.loaded = false;
|
||||||
|
this.loadPromise = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initPlayer(el: HTMLElement, options: RLottieOptions) {
|
private initPlayer(el: RLottiePlayer['el'], options: RLottieOptions) {
|
||||||
const rlPlayer = new RLottiePlayer({
|
const player = new RLottiePlayer({
|
||||||
el,
|
el,
|
||||||
worker: this.workers[this.curWorkerNum++],
|
worker: this.workers[this.curWorkerNum++],
|
||||||
options
|
options
|
||||||
});
|
});
|
||||||
|
|
||||||
this.players[rlPlayer.reqId] = rlPlayer;
|
const {reqId, cacheName} = player;
|
||||||
|
this.players[reqId] = player;
|
||||||
|
|
||||||
|
const playersByCacheName = cacheName ? this.playersByCacheName[cacheName] ??= new Set() : undefined;
|
||||||
|
if(cacheName) {
|
||||||
|
playersByCacheName.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
if(this.curWorkerNum >= this.workers.length) {
|
if(this.curWorkerNum >= this.workers.length) {
|
||||||
this.curWorkerNum = 0;
|
this.curWorkerNum = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
rlPlayer.loadFromData(options.animationData);
|
player.addEventListener('destroy', () => {
|
||||||
|
this.onDestroy(reqId);
|
||||||
|
if(playersByCacheName.delete(player) && !playersByCacheName.size) {
|
||||||
|
delete this.playersByCacheName[cacheName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return rlPlayer;
|
player.loadFromData(options.animationData);
|
||||||
|
|
||||||
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {IS_SAFARI} from '../../environment/userAgent';
|
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
|
||||||
import EventListenerBase from '../../helpers/eventListenerBase';
|
import EventListenerBase from '../../helpers/eventListenerBase';
|
||||||
|
|
||||||
export default class QueryableWorker extends EventListenerBase<{
|
export default class QueryableWorker extends EventListenerBase<{
|
||||||
ready: () => void,
|
ready: () => void,
|
||||||
frame: (reqId: number, frameNo: number, frame: Uint8ClampedArray) => void,
|
frame: (reqId: number, frameNo: number, frame: Uint8ClampedArray | ImageBitmap) => void,
|
||||||
loaded: (reqId: number, frameCount: number, fps: number) => void,
|
loaded: (reqId: number, frameCount: number, fps: number) => void,
|
||||||
error: (reqId: number, error: Error) => void,
|
error: (reqId: number, error: Error) => void,
|
||||||
workerError: (error: ErrorEvent) => void
|
workerError: (error: ErrorEvent) => void
|
||||||
|
@ -40,29 +40,10 @@ export default class QueryableWorker extends EventListenerBase<{
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendQuery(queryMethod: string, ...args: any[]) {
|
public sendQuery(args: any[], transfer?: Transferable[]) {
|
||||||
if(IS_SAFARI) {
|
this.worker.postMessage({
|
||||||
this.worker.postMessage({
|
queryMethod: args.shift(),
|
||||||
queryMethod: queryMethod,
|
queryMethodArguments: args
|
||||||
queryMethodArguments: args
|
}, CAN_USE_TRANSFERABLES ? transfer: undefined);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const transfer: Transferable[] = [];
|
|
||||||
args.forEach((arg) => {
|
|
||||||
if(arg instanceof ArrayBuffer) {
|
|
||||||
transfer.push(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof(arg) === 'object' && arg.buffer instanceof ArrayBuffer) {
|
|
||||||
transfer.push(arg.buffer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log('transfer', transfer);
|
|
||||||
this.worker.postMessage({
|
|
||||||
queryMethod: queryMethod,
|
|
||||||
queryMethodArguments: args
|
|
||||||
}, transfer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
|
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
|
||||||
|
import IS_IMAGE_BITMAP_SUPPORTED from '../../environment/imageBitmapSupport';
|
||||||
import readBlobAsText from '../../helpers/blob/readBlobAsText';
|
import readBlobAsText from '../../helpers/blob/readBlobAsText';
|
||||||
import applyReplacements from './applyReplacements';
|
import applyReplacements from './applyReplacements';
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ export class RLottieItem {
|
||||||
private dead: boolean;
|
private dead: boolean;
|
||||||
// private context: OffscreenCanvasRenderingContext2D;
|
// private context: OffscreenCanvasRenderingContext2D;
|
||||||
|
|
||||||
|
private imageData: ImageData;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private reqId: number,
|
private reqId: number,
|
||||||
private width: number,
|
private width: number,
|
||||||
|
@ -62,10 +65,14 @@ export class RLottieItem {
|
||||||
|
|
||||||
worker.Api.resize(this.handle, this.width, this.height);
|
worker.Api.resize(this.handle, this.width, this.height);
|
||||||
|
|
||||||
reply('loaded', this.reqId, this.frameCount, this.fps);
|
reply(['loaded', this.reqId, this.frameCount, this.fps]);
|
||||||
|
|
||||||
|
if(IS_IMAGE_BITMAP_SUPPORTED) {
|
||||||
|
this.imageData = new ImageData(this.width, this.height);
|
||||||
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('init RLottieItem error:', e);
|
console.error('init RLottieItem error:', e);
|
||||||
reply('error', this.reqId, e);
|
reply(['error', this.reqId, e]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,19 +91,26 @@ export class RLottieItem {
|
||||||
|
|
||||||
const data = _Module.HEAPU8.subarray(bufferPointer, bufferPointer + (this.width * this.height * 4));
|
const data = _Module.HEAPU8.subarray(bufferPointer, bufferPointer + (this.width * this.height * 4));
|
||||||
|
|
||||||
if(!clamped) {
|
if(this.imageData) {
|
||||||
clamped = new Uint8ClampedArray(data);
|
this.imageData.data.set(data);
|
||||||
|
createImageBitmap(this.imageData).then((imageBitmap) => {
|
||||||
|
reply(['frame', this.reqId, frameNo, imageBitmap], [imageBitmap]);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
clamped.set(data);
|
if(!clamped) {
|
||||||
|
clamped = new Uint8ClampedArray(data);
|
||||||
|
} else {
|
||||||
|
clamped.set(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.context.putImageData(new ImageData(clamped, this.width, this.height), 0, 0);
|
||||||
|
|
||||||
|
reply(['frame', this.reqId, frameNo, clamped], [clamped]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.context.putImageData(new ImageData(clamped, this.width, this.height), 0, 0);
|
|
||||||
|
|
||||||
reply('frame', this.reqId, frameNo, clamped);
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('Render error:', e);
|
console.error('Render error:', e);
|
||||||
this.dead = true;
|
this.dead = true;
|
||||||
reply('error', this.reqId, e);
|
reply(['error', this.reqId, e]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +146,7 @@ class RLottieWorker {
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
this.initApi();
|
this.initApi();
|
||||||
reply('ready');
|
reply(['ready']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +188,7 @@ const queryableFunctions = {
|
||||||
item.init(json, frameRate);
|
item.init(json, frameRate);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error('Invalid file for sticker:', json);
|
console.error('Invalid file for sticker:', json);
|
||||||
reply('error', reqId, err);
|
reply(['error', reqId, err]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -193,31 +207,8 @@ const queryableFunctions = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function reply(...args: any[]) {
|
function reply(args: any[], transfer?: Transferable[]) {
|
||||||
if(arguments.length < 1) {
|
postMessage({queryMethodListener: args.shift(), queryMethodArguments: args}, CAN_USE_TRANSFERABLES ? transfer : undefined);
|
||||||
throw new TypeError('reply - not enough arguments');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if(arguments[0] === 'frame') return;
|
|
||||||
|
|
||||||
args = Array.prototype.slice.call(arguments, 1);
|
|
||||||
|
|
||||||
if(!CAN_USE_TRANSFERABLES) {
|
|
||||||
postMessage({queryMethodListener: arguments[0], queryMethodArguments: args});
|
|
||||||
} else {
|
|
||||||
const transfer: ArrayBuffer[] = [];
|
|
||||||
for(let i = 0; i < args.length; ++i) {
|
|
||||||
if(args[i] instanceof ArrayBuffer) {
|
|
||||||
transfer.push(args[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) {
|
|
||||||
transfer.push(args[i].buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
postMessage({queryMethodListener: arguments[0], queryMethodArguments: args}, transfer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage = function(e) {
|
onmessage = function(e) {
|
||||||
|
|
|
@ -10,12 +10,12 @@ import {IS_ANDROID, IS_APPLE_MOBILE, IS_APPLE, IS_SAFARI} from '../../environmen
|
||||||
import EventListenerBase from '../../helpers/eventListenerBase';
|
import EventListenerBase from '../../helpers/eventListenerBase';
|
||||||
import mediaSizes from '../../helpers/mediaSizes';
|
import mediaSizes from '../../helpers/mediaSizes';
|
||||||
import clamp from '../../helpers/number/clamp';
|
import clamp from '../../helpers/number/clamp';
|
||||||
import lottieLoader from './lottieLoader';
|
|
||||||
import QueryableWorker from './queryableWorker';
|
import QueryableWorker from './queryableWorker';
|
||||||
import {AnimationItemGroup} from '../../components/animationIntersector';
|
import {AnimationItemGroup} from '../../components/animationIntersector';
|
||||||
|
import IS_IMAGE_BITMAP_SUPPORTED from '../../environment/imageBitmapSupport';
|
||||||
|
|
||||||
export type RLottieOptions = {
|
export type RLottieOptions = {
|
||||||
container: HTMLElement,
|
container: HTMLElement | HTMLElement[],
|
||||||
canvas?: HTMLCanvasElement,
|
canvas?: HTMLCanvasElement,
|
||||||
autoplay?: boolean,
|
autoplay?: boolean,
|
||||||
animationData: Blob,
|
animationData: Blob,
|
||||||
|
@ -31,27 +31,58 @@ export type RLottieOptions = {
|
||||||
inverseColor?: RLottieColor,
|
inverseColor?: RLottieColor,
|
||||||
name?: string,
|
name?: string,
|
||||||
skipFirstFrameRendering?: boolean,
|
skipFirstFrameRendering?: boolean,
|
||||||
toneIndex?: number
|
toneIndex?: number,
|
||||||
|
sync?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
type RLottieCacheMap = Map<number, Uint8ClampedArray>;
|
type RLottieCacheMap = Map<number, Uint8ClampedArray>;
|
||||||
|
type RLottieCacheMapNew = Map<number, HTMLCanvasElement | ImageBitmap>;
|
||||||
|
type RLottieCacheMapURLs = Map<number, string>;
|
||||||
|
type RLottieCacheItem = {
|
||||||
|
frames: RLottieCacheMap,
|
||||||
|
framesNew: RLottieCacheMapNew,
|
||||||
|
framesURLs: RLottieCacheMapURLs,
|
||||||
|
clearCache: () => void,
|
||||||
|
counter: number
|
||||||
|
};
|
||||||
|
|
||||||
class RLottieCache {
|
class RLottieCache {
|
||||||
private cache: Map<string, {frames: RLottieCacheMap, counter: number}>;
|
private cache: Map<string, RLottieCacheItem>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cache = new Map();
|
this.cache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static createCache(): RLottieCacheItem {
|
||||||
|
const cache: RLottieCacheItem = {
|
||||||
|
frames: new Map(),
|
||||||
|
framesNew: new Map(),
|
||||||
|
framesURLs: new Map(),
|
||||||
|
clearCache: () => {
|
||||||
|
cache.framesNew.forEach((value) => {
|
||||||
|
(value as ImageBitmap).close?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
cache.frames.clear();
|
||||||
|
cache.framesNew.clear();
|
||||||
|
cache.framesURLs.clear();
|
||||||
|
},
|
||||||
|
counter: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
public getCache(name: string) {
|
public getCache(name: string) {
|
||||||
let cache = this.cache.get(name);
|
let cache = this.cache.get(name);
|
||||||
if(!cache) {
|
if(!cache) {
|
||||||
this.cache.set(name, cache = {frames: new Map(), counter: 0});
|
this.cache.set(name, cache = RLottieCache.createCache());
|
||||||
} else {
|
} else {
|
||||||
// console.warn('[RLottieCache] cache will be reused', cache);
|
// console.warn('[RLottieCache] cache will be reused', cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
++cache.counter;
|
++cache.counter;
|
||||||
return cache.frames;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public releaseCache(name: string) {
|
public releaseCache(name: string) {
|
||||||
|
@ -90,6 +121,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
cached: () => void,
|
cached: () => void,
|
||||||
destroy: () => void
|
destroy: () => void
|
||||||
}> {
|
}> {
|
||||||
|
public static CACHE = cache;
|
||||||
private static reqId = 0;
|
private static reqId = 0;
|
||||||
|
|
||||||
public reqId = 0;
|
public reqId = 0;
|
||||||
|
@ -97,8 +129,8 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
private frameCount: number;
|
private frameCount: number;
|
||||||
private fps: number;
|
private fps: number;
|
||||||
private skipDelta: number;
|
private skipDelta: number;
|
||||||
private name: string;
|
public name: string;
|
||||||
private cacheName: string;
|
public cacheName: string;
|
||||||
private toneIndex: number;
|
private toneIndex: number;
|
||||||
|
|
||||||
private worker: QueryableWorker;
|
private worker: QueryableWorker;
|
||||||
|
@ -106,9 +138,9 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
private width = 0;
|
private width = 0;
|
||||||
private height = 0;
|
private height = 0;
|
||||||
|
|
||||||
public el: HTMLElement;
|
public el: HTMLElement[];
|
||||||
public canvas: HTMLCanvasElement;
|
public canvas: HTMLCanvasElement[];
|
||||||
private context: CanvasRenderingContext2D;
|
private contexts: CanvasRenderingContext2D[];
|
||||||
|
|
||||||
public paused = true;
|
public paused = true;
|
||||||
// public paused = false;
|
// public paused = false;
|
||||||
|
@ -127,7 +159,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
// private caching = false;
|
// private caching = false;
|
||||||
// private removed = false;
|
// private removed = false;
|
||||||
|
|
||||||
private frames: RLottieCacheMap;
|
private cache: RLottieCacheItem;
|
||||||
private imageData: ImageData;
|
private imageData: ImageData;
|
||||||
public clamped: Uint8ClampedArray;
|
public clamped: Uint8ClampedArray;
|
||||||
private cachingDelta = 0;
|
private cachingDelta = 0;
|
||||||
|
@ -146,8 +178,11 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
private skipFirstFrameRendering: boolean;
|
private skipFirstFrameRendering: boolean;
|
||||||
private playToFrameOnFrameCallback: (frameNo: number) => void;
|
private playToFrameOnFrameCallback: (frameNo: number) => void;
|
||||||
|
|
||||||
|
public overrideRender: (frame: ImageData | HTMLCanvasElement | ImageBitmap) => void;
|
||||||
|
private renderedFirstFrame: boolean;
|
||||||
|
|
||||||
constructor({el, worker, options}: {
|
constructor({el, worker, options}: {
|
||||||
el: HTMLElement,
|
el: RLottiePlayer['el'],
|
||||||
worker: QueryableWorker,
|
worker: QueryableWorker,
|
||||||
options: RLottieOptions
|
options: RLottieOptions
|
||||||
}) {
|
}) {
|
||||||
|
@ -175,6 +210,10 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
this.skipFirstFrameRendering = options.skipFirstFrameRendering;
|
this.skipFirstFrameRendering = options.skipFirstFrameRendering;
|
||||||
this.toneIndex = options.toneIndex;
|
this.toneIndex = options.toneIndex;
|
||||||
|
|
||||||
|
if(this.name) {
|
||||||
|
this.cacheName = cache.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// * Skip ratio (30fps)
|
// * Skip ratio (30fps)
|
||||||
let skipRatio: number;
|
let skipRatio: number;
|
||||||
if(options.skipRatio !== undefined) skipRatio = options.skipRatio;
|
if(options.skipRatio !== undefined) skipRatio = options.skipRatio;
|
||||||
|
@ -187,33 +226,19 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
// options.needUpscale = true;
|
// options.needUpscale = true;
|
||||||
|
|
||||||
// * Pixel ratio
|
// * Pixel ratio
|
||||||
// const pixelRatio = window.devicePixelRatio;
|
let pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||||
const pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
if(pixelRatio > 1 && !options.needUpscale) {
|
||||||
if(pixelRatio > 1) {
|
if(this.width > 100 && this.height > 100) {
|
||||||
// this.cachingEnabled = true;//this.width < 100 && this.height < 100;
|
if(!IS_APPLE && mediaSizes.isMobile) {
|
||||||
if(options.needUpscale) {
|
pixelRatio = 1;
|
||||||
this.width = Math.round(this.width * pixelRatio);
|
|
||||||
this.height = Math.round(this.height * pixelRatio);
|
|
||||||
} else if(pixelRatio > 1) {
|
|
||||||
if(this.width > 100 && this.height > 100) {
|
|
||||||
if(IS_APPLE || !mediaSizes.isMobile) {
|
|
||||||
/* this.width = Math.round(this.width * (pixelRatio - 1));
|
|
||||||
this.height = Math.round(this.height * (pixelRatio - 1)); */
|
|
||||||
this.width = Math.round(this.width * pixelRatio);
|
|
||||||
this.height = Math.round(this.height * pixelRatio);
|
|
||||||
} else if(pixelRatio > 2.5) {
|
|
||||||
this.width = Math.round(this.width * (pixelRatio - 1.5));
|
|
||||||
this.height = Math.round(this.height * (pixelRatio - 1.5));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.width = Math.round(this.width * Math.max(1.5, pixelRatio - 1.5));
|
|
||||||
this.height = Math.round(this.height * Math.max(1.5, pixelRatio - 1.5));
|
|
||||||
}
|
}
|
||||||
|
} else if(this.width > 60 && this.height > 60) {
|
||||||
|
pixelRatio = Math.max(1.5, pixelRatio - 1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.width = Math.round(this.width);
|
this.width = Math.round(this.width * pixelRatio);
|
||||||
this.height = Math.round(this.height);
|
this.height = Math.round(this.height * pixelRatio);
|
||||||
|
|
||||||
// options.noCache = true;
|
// options.noCache = true;
|
||||||
|
|
||||||
|
@ -230,35 +255,36 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.cachingDelta = Infinity;
|
// this.cachingDelta = Infinity;
|
||||||
|
// this.cachingDelta = 0;
|
||||||
// if(isApple) {
|
// if(isApple) {
|
||||||
// this.cachingDelta = 0; //2 // 50%
|
// this.cachingDelta = 0; //2 // 50%
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/* this.width *= 0.8;
|
|
||||||
this.height *= 0.8; */
|
|
||||||
|
|
||||||
// console.log("RLottiePlayer width:", this.width, this.height, options);
|
|
||||||
if(!this.canvas) {
|
if(!this.canvas) {
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = this.el.map(() => {
|
||||||
this.canvas.classList.add('rlottie');
|
const canvas = document.createElement('canvas');
|
||||||
this.canvas.width = this.width;
|
canvas.classList.add('rlottie');
|
||||||
this.canvas.height = this.height;
|
canvas.width = this.width;
|
||||||
this.canvas.dpr = pixelRatio;
|
canvas.height = this.height;
|
||||||
|
canvas.dpr = pixelRatio;
|
||||||
|
return canvas;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context = this.canvas.getContext('2d');
|
this.contexts = this.canvas.map((canvas) => canvas.getContext('2d'));
|
||||||
|
|
||||||
if(CAN_USE_TRANSFERABLES) {
|
if(!IS_IMAGE_BITMAP_SUPPORTED) {
|
||||||
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
|
this.imageData = new ImageData(this.width, this.height);
|
||||||
|
|
||||||
|
if(CAN_USE_TRANSFERABLES) {
|
||||||
|
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageData = new ImageData(this.width, this.height);
|
|
||||||
|
|
||||||
if(this.name) {
|
if(this.name) {
|
||||||
this.cacheName = cache.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
|
this.cache = cache.getCache(this.cacheName);
|
||||||
this.frames = cache.getCache(this.cacheName);
|
|
||||||
} else {
|
} else {
|
||||||
this.frames = new Map();
|
this.cache = RLottieCache.createCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,20 +293,19 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.cacheName && cache.getCacheCounter(this.cacheName) > 1) { // skip clearing because same sticker can be still visible
|
if(this.cacheName && this.cache.counter > 1) { // skip clearing because same sticker can be still visible
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frames.clear();
|
this.cache.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendQuery(methodName: string, ...args: any[]) {
|
public sendQuery(args: any[]) {
|
||||||
// console.trace('RLottie sendQuery:', methodName);
|
this.worker.sendQuery([args.shift(), this.reqId, ...args]);
|
||||||
this.worker.sendQuery(methodName, this.reqId, ...args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadFromData(data: RLottieOptions['animationData']) {
|
public loadFromData(data: RLottieOptions['animationData']) {
|
||||||
this.sendQuery('loadFromData', data, this.width, this.height, this.toneIndex/* , this.canvas.transferControlToOffscreen() */);
|
this.sendQuery(['loadFromData', data, this.width, this.height, this.toneIndex/* , this.canvas.transferControlToOffscreen() */]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public play() {
|
public play() {
|
||||||
|
@ -288,10 +313,6 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// console.log('RLOTTIE PLAY' + this.reqId);
|
|
||||||
|
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
this.setMainLoop();
|
this.setMainLoop();
|
||||||
}
|
}
|
||||||
|
@ -352,13 +373,10 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
}
|
}
|
||||||
|
|
||||||
public remove() {
|
public remove() {
|
||||||
// alert('remove');
|
|
||||||
lottieLoader.onDestroy(this.reqId);
|
|
||||||
this.pause();
|
this.pause();
|
||||||
this.sendQuery('destroy');
|
this.sendQuery(['destroy']);
|
||||||
if(this.cacheName) cache.releaseCache(this.cacheName);
|
if(this.cacheName) cache.releaseCache(this.cacheName);
|
||||||
this.dispatchEvent('destroy');
|
this.dispatchEvent('destroy');
|
||||||
// this.removed = true;
|
|
||||||
this.cleanup();
|
this.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,40 +405,73 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderFrame2(frame: Uint8ClampedArray, frameNo: number) {
|
public renderFrame2(frame: Uint8ClampedArray | HTMLCanvasElement | ImageBitmap, frameNo: number) {
|
||||||
/* this.setListenerResult('enterFrame', frameNo);
|
/* this.setListenerResult('enterFrame', frameNo);
|
||||||
return; */
|
return; */
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(this.color) {
|
if(frame instanceof Uint8ClampedArray) {
|
||||||
this.applyColor(frame);
|
if(this.color) {
|
||||||
}
|
this.applyColor(frame);
|
||||||
|
}
|
||||||
|
|
||||||
if(this.inverseColor) {
|
if(this.inverseColor) {
|
||||||
this.applyInversing(frame);
|
this.applyInversing(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageData.data.set(frame);
|
this.imageData.data.set(frame);
|
||||||
|
}
|
||||||
|
|
||||||
// this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0);
|
// this.context.putImageData(new ImageData(frame, this.width, this.height), 0, 0);
|
||||||
// let perf = performance.now();
|
this.contexts.forEach((context, idx) => {
|
||||||
this.context.putImageData(this.imageData, 0, 0);
|
let cachedSource: HTMLCanvasElement | ImageBitmap = this.cache.framesNew.get(frameNo);
|
||||||
// console.log('renderFrame2 perf:', performance.now() - perf);
|
if(!(frame instanceof Uint8ClampedArray)) {
|
||||||
|
cachedSource = frame;
|
||||||
|
} else if(idx > 0) {
|
||||||
|
cachedSource = this.canvas[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cachedSource) {
|
||||||
|
// console.log('drawing from data');
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
c.width = context.canvas.width;
|
||||||
|
c.height = context.canvas.height;
|
||||||
|
c.getContext('2d').putImageData(this.imageData, 0, 0);
|
||||||
|
this.cache.framesNew.set(frameNo, c);
|
||||||
|
cachedSource = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.overrideRender && this.renderedFirstFrame) {
|
||||||
|
this.overrideRender(cachedSource || this.imageData);
|
||||||
|
} else if(cachedSource) {
|
||||||
|
// console.log('drawing from canvas');
|
||||||
|
context.clearRect(0, 0, cachedSource.width, cachedSource.height);
|
||||||
|
context.drawImage(cachedSource, 0, 0);
|
||||||
|
} else {
|
||||||
|
context.putImageData(this.imageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.renderedFirstFrame) {
|
||||||
|
this.renderedFirstFrame = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dispatchEvent('enterFrame', frameNo);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error('RLottiePlayer renderFrame error:', err/* , frame */, this.width, this.height);
|
console.error('RLottiePlayer renderFrame error:', err/* , frame */, this.width, this.height);
|
||||||
this.autoplay = false;
|
this.autoplay = false;
|
||||||
this.pause();
|
this.pause();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('set result enterFrame', frameNo);
|
|
||||||
this.dispatchEvent('enterFrame', frameNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderFrame(frame: Uint8ClampedArray, frameNo: number) {
|
public renderFrame(frame: Parameters<RLottiePlayer['renderFrame2']>[0], frameNo: number) {
|
||||||
// console.log('renderFrame', frameNo, this);
|
const canCacheFrame = this.cachingDelta && (frameNo % this.cachingDelta || !frameNo);
|
||||||
if(this.cachingDelta && (frameNo % this.cachingDelta || !frameNo) && !this.frames.has(frameNo)) {
|
if(canCacheFrame) {
|
||||||
this.frames.set(frameNo, new Uint8ClampedArray(frame));// frame;
|
if(frame instanceof Uint8ClampedArray && !this.cache.frames.has(frameNo)) {
|
||||||
|
this.cache.frames.set(frameNo, new Uint8ClampedArray(frame));// frame;
|
||||||
|
} else if(IS_IMAGE_BITMAP_SUPPORTED && frame instanceof ImageBitmap && !this.cache.framesNew.has(frameNo)) {
|
||||||
|
this.cache.framesNew.set(frameNo, frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if(!this.listenerResults.hasOwnProperty('cached')) {
|
/* if(!this.listenerResults.hasOwnProperty('cached')) {
|
||||||
|
@ -434,14 +485,15 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
|
|
||||||
if(this.frInterval) {
|
if(this.frInterval) {
|
||||||
const now = Date.now(), delta = now - this.frThen;
|
const now = Date.now(), delta = now - this.frThen;
|
||||||
// console.log(`renderFrame delta${this.reqId}:`, this, delta, this.frInterval);
|
|
||||||
|
|
||||||
if(delta < 0) {
|
if(delta < 0) {
|
||||||
|
const timeout = this.frInterval > -delta ? -delta % this.frInterval : this.frInterval;
|
||||||
if(this.rafId) clearTimeout(this.rafId);
|
if(this.rafId) clearTimeout(this.rafId);
|
||||||
return this.rafId = window.setTimeout(() => {
|
this.rafId = window.setTimeout(() => {
|
||||||
this.renderFrame2(frame, frameNo);
|
this.renderFrame2(frame, frameNo);
|
||||||
}, this.frInterval > -delta ? -delta % this.frInterval : this.frInterval);
|
}, timeout);
|
||||||
// await new Promise((resolve) => setTimeout(resolve, -delta % this.frInterval));
|
// await new Promise((resolve) => setTimeout(resolve, -delta % this.frInterval));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,15 +501,18 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
}
|
}
|
||||||
|
|
||||||
public requestFrame(frameNo: number) {
|
public requestFrame(frameNo: number) {
|
||||||
const frame = this.frames.get(frameNo);
|
const frame = this.cache.frames.get(frameNo);
|
||||||
if(frame) {
|
const frameNew = this.cache.framesNew.get(frameNo);
|
||||||
|
if(frameNew) {
|
||||||
|
this.renderFrame(frameNew, frameNo);
|
||||||
|
} else if(frame) {
|
||||||
this.renderFrame(frame, frameNo);
|
this.renderFrame(frame, frameNo);
|
||||||
} else {
|
} else {
|
||||||
if(this.clamped && !this.clamped.length) { // fix detached
|
if(this.clamped && !this.clamped.length) { // fix detached
|
||||||
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
|
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendQuery('renderFrame', frameNo, this.clamped);
|
this.sendQuery(['renderFrame', frameNo, this.clamped]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,8 +699,8 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
this.addEventListener('enterFrame', () => {
|
this.addEventListener('enterFrame', () => {
|
||||||
this.dispatchEvent('firstFrame');
|
this.dispatchEvent('firstFrame');
|
||||||
|
|
||||||
if(!this.canvas.parentNode && this.el) {
|
if(!this.canvas[0].parentNode && this.el && !this.overrideRender) {
|
||||||
this.el.appendChild(this.canvas);
|
this.el.forEach((container, idx) => container.append(this.canvas[idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('enterFrame firstFrame');
|
// console.log('enterFrame firstFrame');
|
||||||
|
@ -672,6 +727,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addEventListener('enterFrame', this.frameListener);
|
this.addEventListener('enterFrame', this.frameListener);
|
||||||
|
// setInterval(this.frameListener, this.frInterval);
|
||||||
|
|
||||||
// ! fix autoplaying since there will be no animationIntersector for it,
|
// ! fix autoplaying since there will be no animationIntersector for it,
|
||||||
if(this.group === 'none' && this.autoplay) {
|
if(this.group === 'none' && this.autoplay) {
|
||||||
|
|
|
@ -43,7 +43,8 @@ const onFirstMount = () => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
loadFonts()/* .then(() => new Promise((resolve) => window.requestAnimationFrame(resolve))) */,
|
loadFonts()/* .then(() => new Promise((resolve) => window.requestAnimationFrame(resolve))) */,
|
||||||
import('../lib/appManagers/appDialogsManager')
|
import('../lib/appManagers/appDialogsManager')
|
||||||
]).then(() => {
|
]).then(([_, appDialogsManager]) => {
|
||||||
|
appDialogsManager.default.start();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById('auth-pages').remove();
|
document.getElementById('auth-pages').remove();
|
||||||
}, 1e3);
|
}, 1e3);
|
||||||
|
|
|
@ -1174,6 +1174,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
|
||||||
|
|
||||||
&-subtitle {
|
&-subtitle {
|
||||||
color: var(--secondary-text-color) !important;
|
color: var(--secondary-text-color) !important;
|
||||||
|
height: 1.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.peer-title {
|
.peer-title {
|
||||||
|
@ -1682,7 +1683,8 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
|
||||||
margin-bottom: 1rem; // .25rem is eaten by the last bubble's margin-bottom
|
margin-bottom: 1rem; // .25rem is eaten by the last bubble's margin-bottom
|
||||||
} */
|
} */
|
||||||
|
|
||||||
&:not(.is-channel), &.is-chat {
|
&:not(.is-channel),
|
||||||
|
&.is-chat {
|
||||||
.message {
|
.message {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -608,11 +608,23 @@ $bubble-beside-button-width: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.emoji-big {
|
&.emoji-big {
|
||||||
|
--emoji-size: 1rem;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
|
|
||||||
.bubble-content {
|
.bubble-content {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment {
|
||||||
|
--custom-emoji-size: var(--emoji-size);
|
||||||
|
|
||||||
|
img.emoji {
|
||||||
|
width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
|
max-width: 64px;
|
||||||
|
max-height: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.sticker) {
|
&:not(.sticker) {
|
||||||
.attachment {
|
.attachment {
|
||||||
|
@ -620,6 +632,11 @@ $bubble-beside-button-width: 38px;
|
||||||
padding-bottom: 1.5rem;
|
padding-bottom: 1.5rem;
|
||||||
//max-width: fit-content!important;
|
//max-width: fit-content!important;
|
||||||
max-height: fit-content!important;
|
max-height: fit-content!important;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: var(--emoji-size);
|
||||||
|
|
||||||
span.emoji {
|
span.emoji {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -637,6 +654,10 @@ $bubble-beside-button-width: 38px;
|
||||||
.message {
|
.message {
|
||||||
margin-top: -1.125rem;
|
margin-top: -1.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bubble-content {
|
||||||
|
max-width: unquote('min(420px, 100%)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* &.sticker .bubble-content {
|
/* &.sticker .bubble-content {
|
||||||
|
@ -646,33 +667,6 @@ $bubble-beside-button-width: 38px;
|
||||||
} */
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
&.emoji-1x .attachment {
|
|
||||||
font-size: 96px;
|
|
||||||
|
|
||||||
img.emoji {
|
|
||||||
height: 64px;
|
|
||||||
width: 64px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.emoji-2x .attachment {
|
|
||||||
font-size: 64px;
|
|
||||||
|
|
||||||
img.emoji {
|
|
||||||
height: 48px;
|
|
||||||
width: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.emoji-3x .attachment {
|
|
||||||
font-size: 52px;
|
|
||||||
|
|
||||||
img.emoji {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.just-media {
|
&.just-media {
|
||||||
.bubble-content {
|
.bubble-content {
|
||||||
// cursor: pointer;
|
// cursor: pointer;
|
||||||
|
|
|
@ -153,6 +153,7 @@
|
||||||
&-subtitle {
|
&-subtitle {
|
||||||
font-size: var(--font-size-14);
|
font-size: var(--font-size-14);
|
||||||
line-height: var(--line-height-14);
|
line-height: var(--line-height-14);
|
||||||
|
position: relative; // ! WARNING (for custom emoji)
|
||||||
|
|
||||||
@include text-overflow();
|
@include text-overflow();
|
||||||
}
|
}
|
||||||
|
|
|
@ -479,6 +479,7 @@ ul.chatlist {
|
||||||
.user-title,
|
.user-title,
|
||||||
.user-last-message {
|
.user-last-message {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
position: relative; // * for custom emoji
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|
54
src/scss/partials/_customEmoji.scss
Normal file
54
src/scss/partials/_customEmoji.scss
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
.custom-emoji {
|
||||||
|
display: inline;
|
||||||
|
width: var(--custom-emoji-size);
|
||||||
|
height: var(--custom-emoji-size);
|
||||||
|
min-height: var(--custom-emoji-size);
|
||||||
|
min-width: var(--custom-emoji-size);
|
||||||
|
position: relative;
|
||||||
|
// pointer-events: none;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
min-width: inherit;
|
||||||
|
min-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-sticker,
|
||||||
|
.rlottie {
|
||||||
|
width: inherit !important;
|
||||||
|
height: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-sticker.thumbnail {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-renderer {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
.spoiler {
|
.spoiler {
|
||||||
--anim: .4s ease;
|
--anim: .4s ease;
|
||||||
position: relative;
|
// position: relative; // ! idk what it was for
|
||||||
background-color: var(--spoiler-background-color);
|
background-color: var(--spoiler-background-color);
|
||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
|
@ -23,24 +23,28 @@
|
||||||
font-family: inherit !important;
|
font-family: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.spoilers-container {
|
||||||
&.will-change {
|
.custom-emoji-canvas {
|
||||||
.spoiler {
|
z-index: -1;
|
||||||
// box-shadow: 0 0 var(--spoiler-background-color);
|
|
||||||
|
|
||||||
&-text {
|
|
||||||
filter: blur(6px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// &.will-change {
|
||||||
|
// .spoiler {
|
||||||
|
// // box-shadow: 0 0 var(--spoiler-background-color);
|
||||||
|
|
||||||
|
// &-text {
|
||||||
|
// filter: blur(6px);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
&.is-spoiler-visible {
|
&.is-spoiler-visible {
|
||||||
&.animating {
|
&.animating {
|
||||||
.spoiler {
|
.spoiler {
|
||||||
transition: /* box-shadow var(--anim), */ background-color var(--anim);
|
transition: /* box-shadow var(--anim), */ background-color var(--anim);
|
||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
transition: opacity var(--anim), filter var(--anim);
|
transition: opacity var(--anim)/* , filter var(--anim) */;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,17 +55,17 @@
|
||||||
// box-shadow: 0 0 30px 30px transparent;
|
// box-shadow: 0 0 30px 30px transparent;
|
||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
filter: blur(0);
|
// filter: blur(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.backwards {
|
// &.backwards {
|
||||||
.spoiler-text {
|
// .spoiler-text {
|
||||||
filter: blur(3px);
|
// filter: blur(3px);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.is-spoiler-visible) {
|
&:not(.is-spoiler-visible) {
|
||||||
|
|
|
@ -114,6 +114,8 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||||
--call-button-size: 3.375rem;
|
--call-button-size: 3.375rem;
|
||||||
--call-button-margin: 2rem;
|
--call-button-margin: 2rem;
|
||||||
|
|
||||||
|
--custom-emoji-size: 1.125rem;
|
||||||
|
|
||||||
// https://github.com/overtake/TelegramSwift/blob/5cc7d2475fe4738a6aa0486c23eaf80a89d33b97/submodules/TGUIKit/TGUIKit/PresentationTheme.swift#L2054
|
// https://github.com/overtake/TelegramSwift/blob/5cc7d2475fe4738a6aa0486c23eaf80a89d33b97/submodules/TGUIKit/TGUIKit/PresentationTheme.swift#L2054
|
||||||
--peer-avatar-red-top: #ff885e;
|
--peer-avatar-red-top: #ff885e;
|
||||||
--peer-avatar-red-bottom: #ff516a;
|
--peer-avatar-red-bottom: #ff516a;
|
||||||
|
@ -362,6 +364,7 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||||
@import "partials/reaction";
|
@import "partials/reaction";
|
||||||
@import "partials/stackedAvatars";
|
@import "partials/stackedAvatars";
|
||||||
@import "partials/stickerViewer";
|
@import "partials/stickerViewer";
|
||||||
|
@import "partials/customEmoji";
|
||||||
|
|
||||||
@import "partials/popups/popup";
|
@import "partials/popups/popup";
|
||||||
@import "partials/popups/editAvatar";
|
@import "partials/popups/editAvatar";
|
||||||
|
@ -913,12 +916,13 @@ hr {
|
||||||
|
|
||||||
span.emoji {
|
span.emoji {
|
||||||
display: inline !important;
|
display: inline !important;
|
||||||
vertical-align: unset !important;
|
|
||||||
//line-height: 1em;
|
//line-height: 1em;
|
||||||
//font-size: 1em;
|
//font-size: 1em;
|
||||||
|
|
||||||
font-family: apple color emoji,segoe ui emoji,noto color emoji,android emoji,emojisymbols,emojione mozilla,twemoji mozilla,segoe ui symbol;
|
font-family: apple color emoji,segoe ui emoji,noto color emoji,android emoji,emojisymbols,emojione mozilla,twemoji mozilla,segoe ui symbol;
|
||||||
|
vertical-align: unset !important;
|
||||||
line-height: 1 !important;
|
line-height: 1 !important;
|
||||||
|
// vertical-align: text-top !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-Retina device
|
// non-Retina device
|
||||||
|
|
Loading…
Reference in New Issue
Block a user