Custom emoji
This commit is contained in:
parent
5fc51256e3
commit
62434a06f1
@ -4,6 +4,7 @@
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type {CustomEmojiRendererElement} from '../lib/richTextProcessor/wrapRichText';
|
||||
import rootScope from '../lib/rootScope';
|
||||
import {IS_SAFARI} from '../environment/userAgent';
|
||||
import {MOUNT_CLASS_TO} from '../config/debug';
|
||||
@ -13,14 +14,15 @@ import indexOfAndSplice from '../helpers/array/indexOfAndSplice';
|
||||
import forEachReverse from '../helpers/array/forEachReverse';
|
||||
import idleController from '../helpers/idleController';
|
||||
import appMediaPlaybackController from './appMediaPlaybackController';
|
||||
import {fastRaf} from '../helpers/schedulers';
|
||||
|
||||
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
|
||||
'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 {
|
||||
el: HTMLElement,
|
||||
group: AnimationItemGroup,
|
||||
animation: RLottiePlayer | HTMLVideoElement
|
||||
animation: RLottiePlayer | HTMLVideoElement | CustomEmojiRendererElement
|
||||
};
|
||||
|
||||
export class AnimationIntersector {
|
||||
@ -47,33 +49,35 @@ export class AnimationIntersector {
|
||||
continue;
|
||||
}
|
||||
|
||||
const player = this.byGroups[group as AnimationItemGroup].find((p) => p.el === target);
|
||||
if(player) {
|
||||
if(entry.isIntersecting) {
|
||||
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;
|
||||
const animation = this.byGroups[group as AnimationItemGroup].find((p) => p.el === target);
|
||||
if(!animation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
// console.log('destroy animation');
|
||||
const {el, animation} = player;
|
||||
animation.remove();
|
||||
|
||||
@ -141,21 +144,21 @@ export class AnimationIntersector {
|
||||
this.visible.delete(player);
|
||||
}
|
||||
|
||||
public addAnimation(animation: RLottiePlayer | HTMLVideoElement, group: AnimationItemGroup = '') {
|
||||
const player: AnimationItem = {
|
||||
el: animation instanceof RLottiePlayer ? animation.el : animation,
|
||||
animation: animation,
|
||||
public addAnimation(_animation: AnimationItem['animation'], group: AnimationItemGroup = '') {
|
||||
const animation: AnimationItem = {
|
||||
el: _animation instanceof RLottiePlayer ? _animation.el[0] : (_animation instanceof HTMLVideoElement ? _animation : _animation.canvas),
|
||||
animation: _animation,
|
||||
group
|
||||
};
|
||||
|
||||
if(animation instanceof RLottiePlayer) {
|
||||
if(!rootScope.settings.stickers.loop && animation.loop) {
|
||||
animation.loop = rootScope.settings.stickers.loop;
|
||||
if(_animation instanceof RLottiePlayer) {
|
||||
if(!rootScope.settings.stickers.loop && _animation.loop) {
|
||||
_animation.loop = rootScope.settings.stickers.loop;
|
||||
}
|
||||
}
|
||||
|
||||
(this.byGroups[group as AnimationItemGroup] ??= []).push(player);
|
||||
this.observer.observe(player.el);
|
||||
(this.byGroups[group as AnimationItemGroup] ??= []).push(animation);
|
||||
this.observer.observe(animation.el);
|
||||
}
|
||||
|
||||
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
|
||||
@ -171,8 +174,8 @@ export class AnimationIntersector {
|
||||
for(const group of groups) {
|
||||
const animations = this.byGroups[group];
|
||||
|
||||
forEachReverse(animations, (player) => {
|
||||
this.checkAnimation(player, blurred, destroy);
|
||||
forEachReverse(animations, (animation) => {
|
||||
this.checkAnimation(animation, blurred, destroy);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -180,7 +183,7 @@ export class AnimationIntersector {
|
||||
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
|
||||
const {el, animation, group} = player;
|
||||
// return;
|
||||
if((destroy || (!isInDOM(el) && !this.lockedGroups[group]))/* && false */) {
|
||||
if(destroy || (!this.lockedGroups[group] && !isInDOM(el))) {
|
||||
this.removeAnimation(player);
|
||||
return;
|
||||
}
|
||||
@ -220,17 +223,19 @@ export class AnimationIntersector {
|
||||
|
||||
public refreshGroup(group: AnimationItemGroup) {
|
||||
const animations = this.byGroups[group];
|
||||
if(animations && animations.length) {
|
||||
animations.forEach((animation) => {
|
||||
this.observer.unobserve(animation.el);
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
animations.forEach((animation) => {
|
||||
this.observer.observe(animation.el);
|
||||
});
|
||||
});
|
||||
if(!animations?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
animations.forEach((animation) => {
|
||||
this.observer.unobserve(animation.el);
|
||||
});
|
||||
|
||||
fastRaf(() => {
|
||||
animations.forEach((animation) => {
|
||||
this.observer.observe(animation.el);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public lockIntersectionGroup(group: AnimationItemGroup) {
|
||||
@ -244,7 +249,5 @@ export class AnimationIntersector {
|
||||
}
|
||||
|
||||
const animationIntersector = new AnimationIntersector();
|
||||
if(MOUNT_CLASS_TO) {
|
||||
MOUNT_CLASS_TO.animationIntersector = animationIntersector;
|
||||
}
|
||||
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.animationIntersector = animationIntersector);
|
||||
export default animationIntersector;
|
||||
|
@ -70,7 +70,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
||||
this.content.main.prepend(stub); */
|
||||
|
||||
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;
|
||||
const setCaptionTimeout = () => {
|
||||
|
@ -113,6 +113,7 @@ import PopupPayment from '../popups/payment';
|
||||
import isInDOM from '../../helpers/dom/isInDOM';
|
||||
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||
import attachStickerViewerListeners from '../stickerViewer';
|
||||
import {makeMediaSize, MediaSize} from '../../helpers/mediaSize';
|
||||
|
||||
const USE_MEDIA_TAILS = false;
|
||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||
@ -3434,7 +3435,7 @@ export default class ChatBubbles {
|
||||
const our = this.chat.isOurMessage(message);
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.classList.add('message');
|
||||
messageDiv.classList.add('message', 'spoilers-container');
|
||||
|
||||
const contentWrapper = document.createElement('div');
|
||||
contentWrapper.classList.add('bubble-content-wrapper');
|
||||
@ -3526,54 +3527,68 @@ export default class ChatBubbles {
|
||||
}
|
||||
}
|
||||
|
||||
/* let richText = wrapRichText(messageMessage, {
|
||||
entities: totalEntities
|
||||
}); */
|
||||
let bigEmojis = 0, customEmojiSize: MediaSize;
|
||||
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, {
|
||||
entities: totalEntities,
|
||||
passEntities: this.passEntities
|
||||
passEntities: this.passEntities,
|
||||
loadPromises,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
customEmojiSize
|
||||
});
|
||||
|
||||
let canHaveTail = true;
|
||||
let isStandaloneMedia = false;
|
||||
let needToSetHTML = true;
|
||||
if(totalEntities && !messageMedia) {
|
||||
const emojiEntities = totalEntities.filter((e) => e._ === 'messageEntityEmoji');
|
||||
const strLength = messageMessage.length;
|
||||
const emojiStrLength = emojiEntities.reduce((acc, curr) => acc + curr.length, 0);
|
||||
if(bigEmojis) {
|
||||
if(rootScope.settings.emoji.big) {
|
||||
const sticker = bigEmojis === 1 &&
|
||||
!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) {
|
||||
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);
|
||||
|
||||
setInnerHTML(attachmentDiv, richText);
|
||||
|
||||
bubble.classList.add('emoji-' + emojiEntities.length + 'x');
|
||||
|
||||
bubbleContainer.append(attachmentDiv);
|
||||
}
|
||||
|
||||
bubble.classList.add('is-message-empty', 'emoji-big');
|
||||
isStandaloneMedia = true;
|
||||
canHaveTail = false;
|
||||
needToSetHTML = false;
|
||||
bubbleContainer.append(attachmentDiv);
|
||||
}
|
||||
|
||||
bubble.classList.add('can-have-big-emoji');
|
||||
bubble.classList.add('is-message-empty', 'emoji-big');
|
||||
isStandaloneMedia = true;
|
||||
canHaveTail = false;
|
||||
needToSetHTML = false;
|
||||
}
|
||||
|
||||
/* if(strLength === emojiStrLength) {
|
||||
messageDiv.classList.add('emoji-only');
|
||||
messageDiv.classList.add('message-empty');
|
||||
} */
|
||||
bubble.classList.add('can-have-big-emoji');
|
||||
}
|
||||
|
||||
if(needToSetHTML) {
|
||||
|
@ -50,10 +50,10 @@ export default class TrackingMonkey {
|
||||
|
||||
if(this.idleAnimation) {
|
||||
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 {
|
||||
/* const cb = (frameNo: number) => {
|
||||
if(frameNo <= 1) { */
|
||||
@ -116,7 +116,7 @@ export default class TrackingMonkey {
|
||||
this.animation = _animation;
|
||||
|
||||
if(!this.inputField.value.length) {
|
||||
this.animation.canvas.style.display = 'none';
|
||||
this.animation.canvas[0].style.display = 'none';
|
||||
}
|
||||
|
||||
this.animation.addEventListener('enterFrame', currentFrame => {
|
||||
@ -133,9 +133,9 @@ export default class TrackingMonkey {
|
||||
// animation.curFrame = 0;
|
||||
|
||||
if(this.idleAnimation) {
|
||||
this.idleAnimation.canvas.style.display = '';
|
||||
this.idleAnimation.canvas[0].style.display = '';
|
||||
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 {makeMediaSize} from '../../helpers/mediaSize';
|
||||
import mediaSizes from '../../helpers/mediaSizes';
|
||||
import noop from '../../helpers/noop';
|
||||
import onMediaLoad from '../../helpers/onMediaLoad';
|
||||
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
|
||||
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 getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||
import lottieLoader from '../../lib/rlottie/lottieLoader';
|
||||
import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import type {ThumbCache} from '../../lib/storages/thumbs';
|
||||
import webpWorkerController from '../../lib/webp/webpWorkerController';
|
||||
@ -50,11 +50,13 @@ const EMOJI_EFFECT_MULTIPLIER = 3;
|
||||
|
||||
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,
|
||||
div: HTMLElement,
|
||||
div: HTMLElement | HTMLElement[],
|
||||
middleware?: () => boolean,
|
||||
loadStickerMiddleware?: () => boolean,
|
||||
lazyLoadQueue?: LazyLoadQueue,
|
||||
exportLoad?: boolean,
|
||||
group?: AnimationItemGroup,
|
||||
play?: boolean,
|
||||
onlyThumb?: boolean,
|
||||
@ -74,8 +76,11 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
noPremium?: boolean,
|
||||
withLock?: boolean,
|
||||
relativeEffect?: boolean,
|
||||
loopEffect?: boolean
|
||||
loopEffect?: boolean,
|
||||
isCustomEmoji?: boolean
|
||||
}) {
|
||||
div = Array.isArray(div) ? div : [div];
|
||||
|
||||
const stickerType = doc.sticker;
|
||||
if(stickerType === 1) {
|
||||
asStatic = true;
|
||||
@ -94,13 +99,10 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
lottieLoader.loadLottieWorkers();
|
||||
}
|
||||
|
||||
if(!stickerType) {
|
||||
console.error('wrong doc for wrapSticker!', doc);
|
||||
throw new Error('wrong doc for wrapSticker!');
|
||||
}
|
||||
|
||||
div.dataset.docId = '' + doc.id;
|
||||
div.classList.add('media-sticker-wrapper');
|
||||
div.forEach((div) => {
|
||||
div.dataset.docId = '' + doc.id;
|
||||
div.classList.add('media-sticker-wrapper');
|
||||
});
|
||||
|
||||
/* if(stickerType === 3) {
|
||||
const videoRes = wrapVideo({
|
||||
@ -141,14 +143,16 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
|
||||
const effectThumb = getStickerEffectThumb(doc);
|
||||
if(isOut !== undefined && effectThumb && !isOut) {
|
||||
div.classList.add('reflect-x');
|
||||
div.forEach((div) => div.classList.add('reflect-x'));
|
||||
}
|
||||
|
||||
const willHaveLock = effectThumb && withLock;
|
||||
if(willHaveLock) {
|
||||
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
|
||||
const lockUrl = locksUrls[doc.id];
|
||||
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
||||
div.forEach((div) => {
|
||||
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
|
||||
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
||||
});
|
||||
}
|
||||
|
||||
if(asStatic && stickerType !== 1) {
|
||||
@ -164,13 +168,14 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
const isThumbNeededForType = isAnimated;
|
||||
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 haveThumbCached = false;
|
||||
if((
|
||||
doc.thumbs?.length ||
|
||||
lottieCachedThumb
|
||||
) &&
|
||||
!div.firstElementChild && (
|
||||
!div[0].firstElementChild && (
|
||||
!downloaded ||
|
||||
isThumbNeededForType ||
|
||||
onlyThumb
|
||||
@ -180,8 +185,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
|
||||
// console.log('wrap sticker', thumb, div);
|
||||
|
||||
let thumbImage: HTMLImageElement | HTMLCanvasElement;
|
||||
const afterRender = () => {
|
||||
const afterRender = (div: HTMLElement, thumbImage: HTMLElement) => {
|
||||
if(!div.childElementCount) {
|
||||
thumbImage.classList.add('media-sticker', 'thumbnail');
|
||||
|
||||
@ -193,105 +197,133 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
};
|
||||
|
||||
if('url' in thumb) {
|
||||
thumbImage = new Image();
|
||||
renderImageFromUrl(thumbImage, thumb.url, afterRender);
|
||||
haveThumbCached = true;
|
||||
div.forEach((div) => {
|
||||
const thumbImage = new Image();
|
||||
renderImageFromUrl(thumbImage, (thumb as any).url, () => afterRender(div, thumbImage));
|
||||
});
|
||||
} else if('bytes' in thumb) {
|
||||
if(thumb._ === 'photoPathSize') {
|
||||
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 {
|
||||
if(!thumb.bytes.length) {
|
||||
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) {
|
||||
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 */) {
|
||||
renderImageFromUrl(thumbImage, getPreviewURLFromThumb(doc, thumb, true), afterRender);
|
||||
haveThumbCached = true;
|
||||
r();
|
||||
} else {
|
||||
haveThumbCached = true;
|
||||
webpWorkerController.convert('main-' + doc.id, thumb.bytes).then((bytes) => {
|
||||
managers.appDocsManager.saveWebPConvertedStrippedThumb(doc.id, bytes);
|
||||
(thumb as PhotoSize.photoStrippedSize).bytes = bytes;
|
||||
doc.pFlags.stickerThumbConverted = true;
|
||||
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
if(!div.childElementCount) {
|
||||
renderImageFromUrl(thumbImage, getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true), afterRender);
|
||||
if((middleware && !middleware()) || (div as HTMLElement[])[0].childElementCount) {
|
||||
loadThumbPromise.resolve();
|
||||
return;
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
r();
|
||||
}).catch(() => loadThumbPromise.resolve());
|
||||
}
|
||||
}
|
||||
} else if(((stickerType === 2 && toneIndex <= 0) || stickerType === 3) && (withThumb || onlyThumb)) {
|
||||
const load = async() => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
if((div as HTMLElement[])[0].childElementCount || (middleware && !middleware())) {
|
||||
loadThumbPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const r = () => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
renderImageFromUrl(thumbImage, cacheContext.url, afterRender);
|
||||
const r = (div: HTMLElement, thumbImage: HTMLElement) => {
|
||||
if(div.childElementCount || (middleware && !middleware())) {
|
||||
loadThumbPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
renderImageFromUrl(thumbImage, cacheContext.url, () => afterRender(div, thumbImage));
|
||||
};
|
||||
|
||||
await getCacheContext();
|
||||
if(cacheContext.url) {
|
||||
r();
|
||||
return;
|
||||
} else {
|
||||
const res = getImageFromStrippedThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
||||
thumbImage = res.image;
|
||||
res.loadPromise.then(r);
|
||||
(div as HTMLElement[]).forEach((div) => {
|
||||
if(cacheContext.url) {
|
||||
r(div, new Image());
|
||||
} else if('bytes' in thumb) {
|
||||
const res = getImageFromStrippedThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
||||
res.loadPromise.then(() => r(div, res.image));
|
||||
|
||||
// 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) {
|
||||
lazyLoadQueue.push({div, load});
|
||||
return;
|
||||
lazyLoadQueue.push({div: div[0], load});
|
||||
loadThumbPromise.resolve();
|
||||
return ret;
|
||||
} else {
|
||||
load();
|
||||
|
||||
@ -307,7 +339,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
}
|
||||
|
||||
if(onlyThumb/* || true */) { // for sticker panel
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const middlewareError = makeError('MIDDLEWARE');
|
||||
@ -317,27 +349,14 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
}
|
||||
|
||||
if(stickerType === 2 && !asStatic) {
|
||||
/* if(doc.id === '1860749763008266301') {
|
||||
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})
|
||||
return appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId, thumb: fullThumb})
|
||||
.then(async(blob) => {
|
||||
// console.timeEnd('download sticker' + doc.id);
|
||||
// console.log('loaded sticker:', doc, div/* , blob */);
|
||||
if(middleware && !middleware()) {
|
||||
throw middlewareError;
|
||||
}
|
||||
|
||||
const animation = await lottieLoader.loadAnimationWorker({
|
||||
container: div,
|
||||
container: (div as HTMLElement[])[0],
|
||||
loop: loop && !emoji,
|
||||
autoplay: play,
|
||||
animationData: blob,
|
||||
@ -346,24 +365,25 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
name: 'doc' + doc.id,
|
||||
needUpscale,
|
||||
skipRatio,
|
||||
toneIndex
|
||||
}, group, middleware);
|
||||
toneIndex,
|
||||
sync: isCustomEmoji
|
||||
}, group, loadStickerMiddleware ?? middleware);
|
||||
|
||||
// const deferred = deferredPromise<void>();
|
||||
|
||||
const setLockColor = willHaveLock ? () => {
|
||||
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas);
|
||||
div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
||||
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas[0]);
|
||||
(div as HTMLElement[]).forEach((div) => div.style.setProperty('--lock-url', `url(${lockUrl})`));
|
||||
} : undefined;
|
||||
|
||||
animation.addEventListener('firstFrame', () => {
|
||||
const element = div.firstElementChild;
|
||||
const onFirstFrame = (container: HTMLElement, canvas: HTMLCanvasElement) => {
|
||||
const element = container.firstElementChild;
|
||||
if(needFadeIn !== false) {
|
||||
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
|
||||
}
|
||||
|
||||
const cb = () => {
|
||||
if(element && element !== animation.canvas && element.tagName !== 'DIV') {
|
||||
if(element && element !== canvas && element.tagName !== 'DIV') {
|
||||
element.remove();
|
||||
}
|
||||
};
|
||||
@ -374,29 +394,36 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
}
|
||||
} else {
|
||||
sequentialDom.mutate(() => {
|
||||
animation.canvas.classList.add('fade-in');
|
||||
canvas && canvas.classList.add('fade-in');
|
||||
if(element) {
|
||||
element.classList.add('fade-out');
|
||||
}
|
||||
|
||||
animation.canvas.addEventListener('animationend', () => {
|
||||
(canvas || element).addEventListener('animationend', () => {
|
||||
sequentialDom.mutate(() => {
|
||||
animation.canvas.classList.remove('fade-in');
|
||||
canvas && canvas.classList.remove('fade-in');
|
||||
cb();
|
||||
});
|
||||
}, {once: true});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
animation.addEventListener('firstFrame', () => {
|
||||
const canvas = animation.canvas[0];
|
||||
if(withThumb !== false) {
|
||||
saveLottiePreview(doc, animation.canvas, toneIndex);
|
||||
saveLottiePreview(doc, canvas, toneIndex);
|
||||
}
|
||||
|
||||
if(willHaveLock) {
|
||||
setLockColor();
|
||||
}
|
||||
|
||||
// deferred.resolve();
|
||||
if(!isCustomEmoji) {
|
||||
(div as HTMLElement[]).forEach((container, idx) => {
|
||||
onFirstFrame(container, animation.canvas[idx]);
|
||||
});
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
if(emoji) {
|
||||
@ -409,16 +436,17 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
|
||||
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
||||
|
||||
attachClickEvent(div, async(e) => {
|
||||
const container = (div as HTMLElement[])[0];
|
||||
attachClickEvent(container, async(e) => {
|
||||
cancelEvent(e);
|
||||
const animation = lottieLoader.getAnimation(div);
|
||||
const animation = lottieLoader.getAnimation(container);
|
||||
|
||||
if(animation.paused) {
|
||||
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
||||
if(doc) {
|
||||
const audio = document.createElement('audio');
|
||||
audio.style.display = 'none';
|
||||
div.parentElement.append(audio);
|
||||
container.parentElement.append(audio);
|
||||
|
||||
try {
|
||||
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
||||
@ -455,7 +483,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
middleware,
|
||||
side: isOut ? 'right' : 'left',
|
||||
size: 280,
|
||||
target: div,
|
||||
target: container,
|
||||
play: true,
|
||||
withRandomOffset: true
|
||||
});
|
||||
@ -477,7 +505,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
a.t = (a.t - firstTime) / 1000;
|
||||
});
|
||||
|
||||
const bubble = findUpClassName(div, 'bubble');
|
||||
const bubble = findUpClassName(container, 'bubble');
|
||||
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
||||
_: 'sendMessageEmojiInteraction',
|
||||
msg_id: getServerMessageId(+bubble.dataset.mid),
|
||||
@ -509,34 +537,29 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
// return deferred;
|
||||
// await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||||
});
|
||||
|
||||
// console.timeEnd('render sticker' + doc.id);
|
||||
} else if(asStatic || stickerType === 3) {
|
||||
let media: HTMLElement;
|
||||
if(asStatic) {
|
||||
media = new Image();
|
||||
} else {
|
||||
media = createVideo();
|
||||
(media as HTMLVideoElement).muted = true;
|
||||
|
||||
if(play) {
|
||||
(media as HTMLVideoElement).autoplay = true;
|
||||
const media: HTMLElement[] = (div as HTMLElement[]).map(() => {
|
||||
let media: HTMLElement;
|
||||
if(asStatic) {
|
||||
media = new Image();
|
||||
} else {
|
||||
const video = media = createVideo();
|
||||
video.muted = true;
|
||||
if(play) video.autoplay = true;
|
||||
if(loop) video.loop = true;
|
||||
}
|
||||
|
||||
if(loop) {
|
||||
(media as HTMLVideoElement).loop = true;
|
||||
}
|
||||
}
|
||||
media.classList.add('media-sticker');
|
||||
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) {
|
||||
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) {
|
||||
media.classList.add('fade-in');
|
||||
media.forEach((media) => media.classList.add('fade-in'));
|
||||
}
|
||||
|
||||
return new Promise<HTMLVideoElement | HTMLImageElement>(async(resolve, reject) => {
|
||||
@ -546,7 +569,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
return;
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
const onLoad = (div: HTMLElement, media: HTMLElement, thumbImage: HTMLElement) => {
|
||||
sequentialDom.mutateElement(div, () => {
|
||||
div.append(media);
|
||||
thumbImage && thumbImage.classList.add('fade-out');
|
||||
@ -579,19 +602,22 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
};
|
||||
|
||||
await getCacheContext();
|
||||
if(asStatic) {
|
||||
renderImageFromUrl(media, cacheContext.url, onLoad);
|
||||
} else {
|
||||
(media as HTMLVideoElement).src = cacheContext.url;
|
||||
onMediaLoad(media as HTMLVideoElement).then(onLoad);
|
||||
}
|
||||
media.forEach((media, idx) => {
|
||||
const cb = () => onLoad((div as HTMLElement[])[idx], media, thumbImage[idx]);
|
||||
if(asStatic) {
|
||||
renderImageFromUrl(media, cacheContext.url, cb);
|
||||
} else {
|
||||
(media as HTMLVideoElement).src = cacheContext.url;
|
||||
onMediaLoad(media as HTMLVideoElement).then(cb);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await getCacheContext();
|
||||
if(cacheContext.url) r();
|
||||
else {
|
||||
let promise: Promise<any>;
|
||||
if(stickerType === 2 && asStatic) {
|
||||
if(stickerType !== 1 && asStatic) {
|
||||
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
|
||||
// promise = managers.appDocsManager.getThumbURL(doc, thumb).promise
|
||||
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) ?
|
||||
(lazyLoadQueue.push({div, load}), Promise.resolve()) :
|
||||
(lazyLoadQueue.push({div: div[0], load}), Promise.resolve()) :
|
||||
load();
|
||||
|
||||
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) {
|
||||
attachStickerEffectHandler({
|
||||
container: div,
|
||||
container: div[0],
|
||||
doc,
|
||||
managers,
|
||||
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}: {
|
||||
|
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,
|
||||
round: MediaSize,
|
||||
documentName: MediaSize,
|
||||
invoice: MediaSize
|
||||
invoice: MediaSize,
|
||||
customEmoji: MediaSize
|
||||
};
|
||||
|
||||
export type MediaSizeType = keyof MediaTypeSizes;
|
||||
@ -56,7 +57,8 @@ class MediaSizes extends EventListenerBase<{
|
||||
poll: makeMediaSize(240, 0),
|
||||
round: makeMediaSize(200, 200),
|
||||
documentName: makeMediaSize(200, 0),
|
||||
invoice: makeMediaSize(240, 240)
|
||||
invoice: makeMediaSize(240, 240),
|
||||
customEmoji: makeMediaSize(18, 18)
|
||||
},
|
||||
desktop: {
|
||||
regular: makeMediaSize(420, 340),
|
||||
@ -69,7 +71,8 @@ class MediaSizes extends EventListenerBase<{
|
||||
poll: makeMediaSize(330, 0),
|
||||
round: makeMediaSize(280, 280),
|
||||
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');
|
||||
|
||||
animation.addEventListener('firstFrame', () => {
|
||||
saveLottiePreview(doc, animation.canvas, toneIndex);
|
||||
saveLottiePreview(doc, animation.canvas[0], toneIndex);
|
||||
animation.remove();
|
||||
}, {once: true});
|
||||
});
|
||||
|
@ -49,7 +49,7 @@ class SequentialDom {
|
||||
const promise = isConnected ? this.mutate() : Promise.resolve();
|
||||
|
||||
if(callback !== undefined) {
|
||||
if(isConnected) {
|
||||
if(!isConnected) {
|
||||
callback();
|
||||
} else {
|
||||
promise.then(() => callback());
|
||||
|
@ -234,7 +234,7 @@ export class AppDialogsManager {
|
||||
private managers: AppManagers;
|
||||
private selectTab: ReturnType<typeof horizontalMenu>;
|
||||
|
||||
constructor() {
|
||||
public start() {
|
||||
const managers = this.managers = getProxiedManagers();
|
||||
|
||||
this.contextMenu = new DialogsContextMenu(managers);
|
||||
|
@ -134,6 +134,7 @@ export class AppDocsManager extends AppManager {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'documentAttributeCustomEmoji':
|
||||
case 'documentAttributeSticker':
|
||||
if(attribute.alt !== undefined) {
|
||||
doc.stickerEmojiRaw = attribute.alt;
|
||||
@ -153,7 +154,7 @@ export class AppDocsManager extends AppManager {
|
||||
doc.sticker = 1;
|
||||
} else if(doc.mime_type === 'video/webm') {
|
||||
if(!getEnvironment().IS_WEBM_SUPPORTED) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
doc.type = 'sticker';
|
||||
|
@ -4,6 +4,7 @@
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type {MyDocument} from './appDocsManager';
|
||||
import App from '../../config/app';
|
||||
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
||||
import isObject from '../../helpers/object/isObject';
|
||||
@ -13,6 +14,8 @@ import fixEmoji from '../richTextProcessor/fixEmoji';
|
||||
import SearchIndex from '../searchIndex';
|
||||
import stateStorage from '../stateStorage';
|
||||
import {AppManager} from './manager';
|
||||
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
|
||||
import pause from '../../helpers/schedulers/pause';
|
||||
|
||||
type EmojiLangPack = {
|
||||
keywords: {
|
||||
@ -44,6 +47,9 @@ export class AppEmojiManager extends AppManager {
|
||||
private recent: string[];
|
||||
private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>;
|
||||
|
||||
private getCustomEmojiDocumentsPromise: Promise<any>;
|
||||
private getCustomEmojiDocumentPromises: Map<DocId, CancellablePromise<MyDocument>> = new Map();
|
||||
|
||||
/* public getPopularEmoji() {
|
||||
return stateStorage.get('emojis_popular').then((popEmojis) => {
|
||||
var result = []
|
||||
@ -230,4 +236,61 @@ export class AppEmojiManager extends AppManager {
|
||||
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 I18n, {i18n, join, LangPackKey} from '../langPack';
|
||||
import {ChatFull, ChatInvite, ChatParticipant, ChatParticipants, Message, MessageAction, MessageMedia, SendMessageAction} from '../../layer';
|
||||
import {hslaStringToHex} from '../../helpers/color';
|
||||
import PeerTitle from '../../components/peerTitle';
|
||||
import PopupPeer from '../../components/popups/peer';
|
||||
import blurActiveElement from '../../helpers/dom/blurActiveElement';
|
||||
@ -92,15 +91,6 @@ import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount
|
||||
import findUpClassName from '../../helpers/dom/findUpClassName';
|
||||
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
||||
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';
|
||||
|
||||
@ -380,7 +370,7 @@ export class AppImManager extends EventListenerBase<{
|
||||
|
||||
(window as any).onSpoilerClick = (e: MouseEvent) => {
|
||||
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 isVisible = parentElement.classList.contains(className);
|
||||
|
@ -2789,9 +2789,9 @@ export class AppMessagesManager extends AppManager {
|
||||
}
|
||||
}
|
||||
|
||||
if(isMessage && !unsupported && message.entities) {
|
||||
unsupported = message.entities.some((entity) => entity._ === 'messageEntityCustomEmoji');
|
||||
}
|
||||
// if(isMessage && !unsupported && message.entities) {
|
||||
// unsupported = message.entities.some((entity) => entity._ === 'messageEntityCustomEmoji');
|
||||
// }
|
||||
|
||||
if(isMessage && unsupported) {
|
||||
message.media = {_: 'messageMediaUnsupported'};
|
||||
|
@ -11,7 +11,7 @@ import deepEqual from '../../helpers/object/deepEqual';
|
||||
import {AppManager} from '../appManagers/manager';
|
||||
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 type referenceContextProfilePhoto = {
|
||||
type: 'profilePhoto',
|
||||
@ -36,6 +36,11 @@ export namespace ReferenceContext {
|
||||
type: 'userFull',
|
||||
userId: UserId
|
||||
};
|
||||
|
||||
export type referenceContextCustomEmoji = {
|
||||
type: 'customEmoji',
|
||||
docId: DocId
|
||||
};
|
||||
}
|
||||
|
||||
export type ReferenceBytes = Photo.photo['file_reference'];
|
||||
@ -150,6 +155,10 @@ export class ReferenceDatabase extends AppManager {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'customEmoji': {
|
||||
promise = this.appEmojiManager.getCustomEmojiDocuments([context.docId]);
|
||||
}
|
||||
|
||||
default: {
|
||||
this.log.warn('refreshReference: not implemented context', context);
|
||||
return Promise.reject();
|
||||
|
@ -79,7 +79,6 @@ export default function parseEntities(text: string) {
|
||||
length: 1
|
||||
});
|
||||
} else if(match[8]) { // Emoji
|
||||
// console.log('hit', match[8]);
|
||||
const unified = getEmojiUnified(match[8]);
|
||||
if(unified) {
|
||||
entities.push({
|
||||
|
@ -19,6 +19,237 @@ import setBlankToAnchor from './setBlankToAnchor';
|
||||
import wrapUrl from './wrapUrl';
|
||||
import EMOJI_VERSIONS_SUPPORTED from '../../environment/emojiVersionsSupport';
|
||||
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)
|
||||
@ -46,7 +277,13 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
text: string,
|
||||
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();
|
||||
if(!text) {
|
||||
@ -59,6 +296,8 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
text
|
||||
};
|
||||
|
||||
const customEmojis = options.customEmojis ??= {};
|
||||
|
||||
const entities = options.entities ??= parseEntities(nasty.text);
|
||||
|
||||
const passEntities = options.passEntities ??= {};
|
||||
@ -217,6 +456,25 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
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': {
|
||||
let isSupported = IS_EMOJI_SUPPORTED;
|
||||
if(isSupported) {
|
||||
@ -287,7 +545,8 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
if(nextEntity?._ === 'messageEntityUrl' &&
|
||||
nextEntity.length === entity.length &&
|
||||
nextEntity.offset === entity.offset) {
|
||||
nasty.i++;
|
||||
nasty.lastEntity = nextEntity;
|
||||
++nasty.i;
|
||||
}
|
||||
|
||||
if(url !== fullEntityText) {
|
||||
@ -389,6 +648,14 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
const encoded = encodeSpoiler(nasty.text, entity);
|
||||
nasty.text = encoded.text;
|
||||
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) {
|
||||
element = document.createElement('span');
|
||||
element.style.fontFamily = 'spoiler';
|
||||
@ -464,6 +731,159 @@ export default function wrapRichText(text: string, options: Partial<{
|
||||
(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;
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,9 @@ import {logger, LogTypes} from '../logger';
|
||||
import RLottiePlayer, {RLottieOptions} from './rlottiePlayer';
|
||||
import QueryableWorker from './queryableWorker';
|
||||
import blobConstruct from '../../helpers/blob/blobConstruct';
|
||||
import rootScope from '../rootScope';
|
||||
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' |
|
||||
'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' |
|
||||
@ -21,12 +22,12 @@ export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' |
|
||||
'voice_outlined2' | 'voip_filled' | 'voice_mini';
|
||||
|
||||
export class LottieLoader {
|
||||
private isWebAssemblySupported = typeof(WebAssembly) !== 'undefined';
|
||||
private loadPromise: Promise<void> = !this.isWebAssemblySupported ? Promise.reject() : undefined;
|
||||
private loadPromise: Promise<void> = !IS_WEB_ASSEMBLY_SUPPORTED ? Promise.reject() : undefined;
|
||||
private loaded = false;
|
||||
|
||||
private workersLimit = 4;
|
||||
private players: {[reqId: number]: RLottiePlayer} = {};
|
||||
private playersByCacheName: {[cacheName: string]: Set<RLottiePlayer>} = {};
|
||||
|
||||
private workers: QueryableWorker[] = [];
|
||||
private curWorkerNum = 0;
|
||||
@ -35,7 +36,7 @@ export class LottieLoader {
|
||||
|
||||
public getAnimation(element: HTMLElement) {
|
||||
for(const i in this.players) {
|
||||
if(this.players[i].el === element) {
|
||||
if(this.players[i].el.includes(element)) {
|
||||
return this.players[i];
|
||||
}
|
||||
}
|
||||
@ -91,7 +92,7 @@ export class LottieLoader {
|
||||
}
|
||||
|
||||
public loadAnimationFromURL(params: Omit<RLottieOptions, 'animationData'>, url: string): Promise<RLottiePlayer> {
|
||||
if(!this.isWebAssemblySupported) {
|
||||
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||
return this.loadPromise as any;
|
||||
}
|
||||
|
||||
@ -136,22 +137,30 @@ export class LottieLoader {
|
||||
group: AnimationItemGroup = params.group || '',
|
||||
middleware?: () => boolean
|
||||
): Promise<RLottiePlayer> {
|
||||
if(!this.isWebAssemblySupported) {
|
||||
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||
return this.loadPromise as any;
|
||||
}
|
||||
// params.autoplay = true;
|
||||
|
||||
if(!this.loaded) {
|
||||
await this.loadLottieWorkers();
|
||||
}
|
||||
|
||||
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) {
|
||||
params.width = parseInt(params.container.style.width);
|
||||
params.height = parseInt(params.container.style.height);
|
||||
params.width = parseInt(containers[0].style.width);
|
||||
params.height = parseInt(containers[0].style.height);
|
||||
}
|
||||
|
||||
if(!params.width || !params.height) {
|
||||
@ -160,7 +169,7 @@ export class LottieLoader {
|
||||
|
||||
params.group = group;
|
||||
|
||||
const player = this.initPlayer(params.container, params);
|
||||
const player = this.initPlayer(containers, params);
|
||||
|
||||
if(group !== 'none') {
|
||||
animationIntersector.addAnimation(player, group);
|
||||
@ -170,42 +179,41 @@ export class LottieLoader {
|
||||
}
|
||||
|
||||
private onPlayerLoaded = (reqId: number, frameCount: number, fps: number) => {
|
||||
const rlPlayer = this.players[reqId];
|
||||
if(!rlPlayer) {
|
||||
const player = this.players[reqId];
|
||||
if(!player) {
|
||||
this.log.warn('onPlayerLoaded on destroyed player:', reqId, frameCount);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.debug('onPlayerLoaded');
|
||||
rlPlayer.onLoad(frameCount, fps);
|
||||
// rlPlayer.addListener('firstFrame', () => {
|
||||
// animationIntersector.addAnimation(player, group);
|
||||
// }, true);
|
||||
player.onLoad(frameCount, fps);
|
||||
};
|
||||
|
||||
private onFrame = (reqId: number, frameNo: number, frame: Uint8ClampedArray) => {
|
||||
const rlPlayer = this.players[reqId];
|
||||
if(!rlPlayer) {
|
||||
private onFrame = (reqId: number, frameNo: number, frame: Uint8ClampedArray | ImageBitmap) => {
|
||||
const player = this.players[reqId];
|
||||
if(!player) {
|
||||
this.log.warn('onFrame on destroyed player:', reqId, frameNo);
|
||||
return;
|
||||
}
|
||||
|
||||
if(rlPlayer.clamped !== undefined) {
|
||||
rlPlayer.clamped = frame;
|
||||
if(player.clamped !== undefined && frame instanceof Uint8ClampedArray) {
|
||||
player.clamped = frame;
|
||||
}
|
||||
|
||||
rlPlayer.renderFrame(frame, frameNo);
|
||||
player.renderFrame(frame, frameNo);
|
||||
};
|
||||
|
||||
private onPlayerError = (reqId: number, error: Error) => {
|
||||
const rlPlayer = this.players[reqId];
|
||||
if(rlPlayer) {
|
||||
// ! will need refactoring later, this is not the best way to remove the animation
|
||||
const animations = animationIntersector.getAnimations(rlPlayer.el);
|
||||
animations.forEach((animation) => {
|
||||
animationIntersector.checkAnimation(animation, true, true);
|
||||
});
|
||||
const player = this.players[reqId];
|
||||
if(!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ! 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) {
|
||||
@ -213,6 +221,10 @@ export class LottieLoader {
|
||||
}
|
||||
|
||||
public destroyWorkers() {
|
||||
if(!IS_WEB_ASSEMBLY_SUPPORTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.workers.forEach((worker, idx) => {
|
||||
worker.terminate();
|
||||
this.log('worker #' + idx + ' terminated');
|
||||
@ -220,23 +232,40 @@ export class LottieLoader {
|
||||
|
||||
this.log('workers destroyed');
|
||||
this.workers.length = 0;
|
||||
this.curWorkerNum = 0;
|
||||
this.loaded = false;
|
||||
this.loadPromise = undefined;
|
||||
}
|
||||
|
||||
private initPlayer(el: HTMLElement, options: RLottieOptions) {
|
||||
const rlPlayer = new RLottiePlayer({
|
||||
private initPlayer(el: RLottiePlayer['el'], options: RLottieOptions) {
|
||||
const player = new RLottiePlayer({
|
||||
el,
|
||||
worker: this.workers[this.curWorkerNum++],
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
|
||||
import {IS_SAFARI} from '../../environment/userAgent';
|
||||
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
|
||||
import EventListenerBase from '../../helpers/eventListenerBase';
|
||||
|
||||
export default class QueryableWorker extends EventListenerBase<{
|
||||
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,
|
||||
error: (reqId: number, error: Error) => void,
|
||||
workerError: (error: ErrorEvent) => void
|
||||
@ -40,29 +40,10 @@ export default class QueryableWorker extends EventListenerBase<{
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
public sendQuery(queryMethod: string, ...args: any[]) {
|
||||
if(IS_SAFARI) {
|
||||
this.worker.postMessage({
|
||||
queryMethod: queryMethod,
|
||||
queryMethodArguments: args
|
||||
});
|
||||
} 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);
|
||||
}
|
||||
public sendQuery(args: any[], transfer?: Transferable[]) {
|
||||
this.worker.postMessage({
|
||||
queryMethod: args.shift(),
|
||||
queryMethodArguments: args
|
||||
}, CAN_USE_TRANSFERABLES ? transfer: undefined);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import CAN_USE_TRANSFERABLES from '../../environment/canUseTransferables';
|
||||
import IS_IMAGE_BITMAP_SUPPORTED from '../../environment/imageBitmapSupport';
|
||||
import readBlobAsText from '../../helpers/blob/readBlobAsText';
|
||||
import applyReplacements from './applyReplacements';
|
||||
|
||||
@ -28,6 +29,8 @@ export class RLottieItem {
|
||||
private dead: boolean;
|
||||
// private context: OffscreenCanvasRenderingContext2D;
|
||||
|
||||
private imageData: ImageData;
|
||||
|
||||
constructor(
|
||||
private reqId: number,
|
||||
private width: number,
|
||||
@ -62,10 +65,14 @@ export class RLottieItem {
|
||||
|
||||
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) {
|
||||
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));
|
||||
|
||||
if(!clamped) {
|
||||
clamped = new Uint8ClampedArray(data);
|
||||
if(this.imageData) {
|
||||
this.imageData.data.set(data);
|
||||
createImageBitmap(this.imageData).then((imageBitmap) => {
|
||||
reply(['frame', this.reqId, frameNo, imageBitmap], [imageBitmap]);
|
||||
});
|
||||
} 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) {
|
||||
console.error('Render error:', e);
|
||||
this.dead = true;
|
||||
reply('error', this.reqId, e);
|
||||
reply(['error', this.reqId, e]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +146,7 @@ class RLottieWorker {
|
||||
|
||||
public init() {
|
||||
this.initApi();
|
||||
reply('ready');
|
||||
reply(['ready']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +188,7 @@ const queryableFunctions = {
|
||||
item.init(json, frameRate);
|
||||
} catch(err) {
|
||||
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[]) {
|
||||
if(arguments.length < 1) {
|
||||
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);
|
||||
}
|
||||
function reply(args: any[], transfer?: Transferable[]) {
|
||||
postMessage({queryMethodListener: args.shift(), queryMethodArguments: args}, CAN_USE_TRANSFERABLES ? transfer : undefined);
|
||||
}
|
||||
|
||||
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 mediaSizes from '../../helpers/mediaSizes';
|
||||
import clamp from '../../helpers/number/clamp';
|
||||
import lottieLoader from './lottieLoader';
|
||||
import QueryableWorker from './queryableWorker';
|
||||
import {AnimationItemGroup} from '../../components/animationIntersector';
|
||||
import IS_IMAGE_BITMAP_SUPPORTED from '../../environment/imageBitmapSupport';
|
||||
|
||||
export type RLottieOptions = {
|
||||
container: HTMLElement,
|
||||
container: HTMLElement | HTMLElement[],
|
||||
canvas?: HTMLCanvasElement,
|
||||
autoplay?: boolean,
|
||||
animationData: Blob,
|
||||
@ -31,27 +31,58 @@ export type RLottieOptions = {
|
||||
inverseColor?: RLottieColor,
|
||||
name?: string,
|
||||
skipFirstFrameRendering?: boolean,
|
||||
toneIndex?: number
|
||||
toneIndex?: number,
|
||||
sync?: boolean
|
||||
};
|
||||
|
||||
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 {
|
||||
private cache: Map<string, {frames: RLottieCacheMap, counter: number}>;
|
||||
private cache: Map<string, RLottieCacheItem>;
|
||||
|
||||
constructor() {
|
||||
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) {
|
||||
let cache = this.cache.get(name);
|
||||
if(!cache) {
|
||||
this.cache.set(name, cache = {frames: new Map(), counter: 0});
|
||||
this.cache.set(name, cache = RLottieCache.createCache());
|
||||
} else {
|
||||
// console.warn('[RLottieCache] cache will be reused', cache);
|
||||
}
|
||||
|
||||
++cache.counter;
|
||||
return cache.frames;
|
||||
return cache;
|
||||
}
|
||||
|
||||
public releaseCache(name: string) {
|
||||
@ -90,6 +121,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
cached: () => void,
|
||||
destroy: () => void
|
||||
}> {
|
||||
public static CACHE = cache;
|
||||
private static reqId = 0;
|
||||
|
||||
public reqId = 0;
|
||||
@ -97,8 +129,8 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
private frameCount: number;
|
||||
private fps: number;
|
||||
private skipDelta: number;
|
||||
private name: string;
|
||||
private cacheName: string;
|
||||
public name: string;
|
||||
public cacheName: string;
|
||||
private toneIndex: number;
|
||||
|
||||
private worker: QueryableWorker;
|
||||
@ -106,9 +138,9 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
private width = 0;
|
||||
private height = 0;
|
||||
|
||||
public el: HTMLElement;
|
||||
public canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
public el: HTMLElement[];
|
||||
public canvas: HTMLCanvasElement[];
|
||||
private contexts: CanvasRenderingContext2D[];
|
||||
|
||||
public paused = true;
|
||||
// public paused = false;
|
||||
@ -127,7 +159,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
// private caching = false;
|
||||
// private removed = false;
|
||||
|
||||
private frames: RLottieCacheMap;
|
||||
private cache: RLottieCacheItem;
|
||||
private imageData: ImageData;
|
||||
public clamped: Uint8ClampedArray;
|
||||
private cachingDelta = 0;
|
||||
@ -146,8 +178,11 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
private skipFirstFrameRendering: boolean;
|
||||
private playToFrameOnFrameCallback: (frameNo: number) => void;
|
||||
|
||||
public overrideRender: (frame: ImageData | HTMLCanvasElement | ImageBitmap) => void;
|
||||
private renderedFirstFrame: boolean;
|
||||
|
||||
constructor({el, worker, options}: {
|
||||
el: HTMLElement,
|
||||
el: RLottiePlayer['el'],
|
||||
worker: QueryableWorker,
|
||||
options: RLottieOptions
|
||||
}) {
|
||||
@ -175,6 +210,10 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
this.skipFirstFrameRendering = options.skipFirstFrameRendering;
|
||||
this.toneIndex = options.toneIndex;
|
||||
|
||||
if(this.name) {
|
||||
this.cacheName = cache.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
|
||||
}
|
||||
|
||||
// * Skip ratio (30fps)
|
||||
let skipRatio: number;
|
||||
if(options.skipRatio !== undefined) skipRatio = options.skipRatio;
|
||||
@ -187,33 +226,19 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
// options.needUpscale = true;
|
||||
|
||||
// * Pixel ratio
|
||||
// const pixelRatio = window.devicePixelRatio;
|
||||
const pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||
if(pixelRatio > 1) {
|
||||
// this.cachingEnabled = true;//this.width < 100 && this.height < 100;
|
||||
if(options.needUpscale) {
|
||||
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));
|
||||
let pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||
if(pixelRatio > 1 && !options.needUpscale) {
|
||||
if(this.width > 100 && this.height > 100) {
|
||||
if(!IS_APPLE && mediaSizes.isMobile) {
|
||||
pixelRatio = 1;
|
||||
}
|
||||
} else if(this.width > 60 && this.height > 60) {
|
||||
pixelRatio = Math.max(1.5, pixelRatio - 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
this.width = Math.round(this.width);
|
||||
this.height = Math.round(this.height);
|
||||
this.width = Math.round(this.width * pixelRatio);
|
||||
this.height = Math.round(this.height * pixelRatio);
|
||||
|
||||
// options.noCache = true;
|
||||
|
||||
@ -230,35 +255,36 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
}
|
||||
|
||||
// this.cachingDelta = Infinity;
|
||||
// this.cachingDelta = 0;
|
||||
// if(isApple) {
|
||||
// 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) {
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.canvas.classList.add('rlottie');
|
||||
this.canvas.width = this.width;
|
||||
this.canvas.height = this.height;
|
||||
this.canvas.dpr = pixelRatio;
|
||||
this.canvas = this.el.map(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.classList.add('rlottie');
|
||||
canvas.width = this.width;
|
||||
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) {
|
||||
this.clamped = new Uint8ClampedArray(this.width * this.height * 4);
|
||||
if(!IS_IMAGE_BITMAP_SUPPORTED) {
|
||||
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) {
|
||||
this.cacheName = cache.generateName(this.name, this.width, this.height, this.color, this.toneIndex);
|
||||
this.frames = cache.getCache(this.cacheName);
|
||||
this.cache = cache.getCache(this.cacheName);
|
||||
} else {
|
||||
this.frames = new Map();
|
||||
this.cache = RLottieCache.createCache();
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,20 +293,19 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
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;
|
||||
}
|
||||
|
||||
this.frames.clear();
|
||||
this.cache.clearCache();
|
||||
}
|
||||
|
||||
public sendQuery(methodName: string, ...args: any[]) {
|
||||
// console.trace('RLottie sendQuery:', methodName);
|
||||
this.worker.sendQuery(methodName, this.reqId, ...args);
|
||||
public sendQuery(args: any[]) {
|
||||
this.worker.sendQuery([args.shift(), this.reqId, ...args]);
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -288,10 +313,6 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
return;
|
||||
}
|
||||
|
||||
// return;
|
||||
|
||||
// console.log('RLOTTIE PLAY' + this.reqId);
|
||||
|
||||
this.paused = false;
|
||||
this.setMainLoop();
|
||||
}
|
||||
@ -352,13 +373,10 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
}
|
||||
|
||||
public remove() {
|
||||
// alert('remove');
|
||||
lottieLoader.onDestroy(this.reqId);
|
||||
this.pause();
|
||||
this.sendQuery('destroy');
|
||||
this.sendQuery(['destroy']);
|
||||
if(this.cacheName) cache.releaseCache(this.cacheName);
|
||||
this.dispatchEvent('destroy');
|
||||
// this.removed = true;
|
||||
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);
|
||||
return; */
|
||||
|
||||
try {
|
||||
if(this.color) {
|
||||
this.applyColor(frame);
|
||||
}
|
||||
if(frame instanceof Uint8ClampedArray) {
|
||||
if(this.color) {
|
||||
this.applyColor(frame);
|
||||
}
|
||||
|
||||
if(this.inverseColor) {
|
||||
this.applyInversing(frame);
|
||||
}
|
||||
if(this.inverseColor) {
|
||||
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);
|
||||
// let perf = performance.now();
|
||||
this.context.putImageData(this.imageData, 0, 0);
|
||||
// console.log('renderFrame2 perf:', performance.now() - perf);
|
||||
this.contexts.forEach((context, idx) => {
|
||||
let cachedSource: HTMLCanvasElement | ImageBitmap = this.cache.framesNew.get(frameNo);
|
||||
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) {
|
||||
console.error('RLottiePlayer renderFrame error:', err/* , frame */, this.width, this.height);
|
||||
this.autoplay = false;
|
||||
this.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('set result enterFrame', frameNo);
|
||||
this.dispatchEvent('enterFrame', frameNo);
|
||||
}
|
||||
|
||||
public renderFrame(frame: Uint8ClampedArray, frameNo: number) {
|
||||
// console.log('renderFrame', frameNo, this);
|
||||
if(this.cachingDelta && (frameNo % this.cachingDelta || !frameNo) && !this.frames.has(frameNo)) {
|
||||
this.frames.set(frameNo, new Uint8ClampedArray(frame));// frame;
|
||||
public renderFrame(frame: Parameters<RLottiePlayer['renderFrame2']>[0], frameNo: number) {
|
||||
const canCacheFrame = this.cachingDelta && (frameNo % this.cachingDelta || !frameNo);
|
||||
if(canCacheFrame) {
|
||||
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')) {
|
||||
@ -434,14 +485,15 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
|
||||
if(this.frInterval) {
|
||||
const now = Date.now(), delta = now - this.frThen;
|
||||
// console.log(`renderFrame delta${this.reqId}:`, this, delta, this.frInterval);
|
||||
|
||||
if(delta < 0) {
|
||||
const timeout = this.frInterval > -delta ? -delta % this.frInterval : this.frInterval;
|
||||
if(this.rafId) clearTimeout(this.rafId);
|
||||
return this.rafId = window.setTimeout(() => {
|
||||
this.rafId = window.setTimeout(() => {
|
||||
this.renderFrame2(frame, frameNo);
|
||||
}, this.frInterval > -delta ? -delta % this.frInterval : this.frInterval);
|
||||
}, timeout);
|
||||
// await new Promise((resolve) => setTimeout(resolve, -delta % this.frInterval));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,15 +501,18 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
}
|
||||
|
||||
public requestFrame(frameNo: number) {
|
||||
const frame = this.frames.get(frameNo);
|
||||
if(frame) {
|
||||
const frame = this.cache.frames.get(frameNo);
|
||||
const frameNew = this.cache.framesNew.get(frameNo);
|
||||
if(frameNew) {
|
||||
this.renderFrame(frameNew, frameNo);
|
||||
} else if(frame) {
|
||||
this.renderFrame(frame, frameNo);
|
||||
} else {
|
||||
if(this.clamped && !this.clamped.length) { // fix detached
|
||||
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.dispatchEvent('firstFrame');
|
||||
|
||||
if(!this.canvas.parentNode && this.el) {
|
||||
this.el.appendChild(this.canvas);
|
||||
if(!this.canvas[0].parentNode && this.el && !this.overrideRender) {
|
||||
this.el.forEach((container, idx) => container.append(this.canvas[idx]));
|
||||
}
|
||||
|
||||
// console.log('enterFrame firstFrame');
|
||||
@ -672,6 +727,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
};
|
||||
|
||||
this.addEventListener('enterFrame', this.frameListener);
|
||||
// setInterval(this.frameListener, this.frInterval);
|
||||
|
||||
// ! fix autoplaying since there will be no animationIntersector for it,
|
||||
if(this.group === 'none' && this.autoplay) {
|
||||
|
@ -43,7 +43,8 @@ const onFirstMount = () => {
|
||||
return Promise.all([
|
||||
loadFonts()/* .then(() => new Promise((resolve) => window.requestAnimationFrame(resolve))) */,
|
||||
import('../lib/appManagers/appDialogsManager')
|
||||
]).then(() => {
|
||||
]).then(([_, appDialogsManager]) => {
|
||||
appDialogsManager.default.start();
|
||||
setTimeout(() => {
|
||||
document.getElementById('auth-pages').remove();
|
||||
}, 1e3);
|
||||
|
@ -1174,6 +1174,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
|
||||
|
||||
&-subtitle {
|
||||
color: var(--secondary-text-color) !important;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.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
|
||||
} */
|
||||
|
||||
&:not(.is-channel), &.is-chat {
|
||||
&:not(.is-channel),
|
||||
&.is-chat {
|
||||
.message {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
@ -608,11 +608,23 @@ $bubble-beside-button-width: 38px;
|
||||
}
|
||||
|
||||
&.emoji-big {
|
||||
--emoji-size: 1rem;
|
||||
font-size: 0;
|
||||
|
||||
.bubble-content {
|
||||
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) {
|
||||
.attachment {
|
||||
@ -620,6 +632,11 @@ $bubble-beside-button-width: 38px;
|
||||
padding-bottom: 1.5rem;
|
||||
//max-width: 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 {
|
||||
height: auto;
|
||||
@ -637,6 +654,10 @@ $bubble-beside-button-width: 38px;
|
||||
.message {
|
||||
margin-top: -1.125rem;
|
||||
}
|
||||
|
||||
.bubble-content {
|
||||
max-width: unquote('min(420px, 100%)');
|
||||
}
|
||||
}
|
||||
|
||||
/* &.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 {
|
||||
.bubble-content {
|
||||
// cursor: pointer;
|
||||
|
@ -153,6 +153,7 @@
|
||||
&-subtitle {
|
||||
font-size: var(--font-size-14);
|
||||
line-height: var(--line-height-14);
|
||||
position: relative; // ! WARNING (for custom emoji)
|
||||
|
||||
@include text-overflow();
|
||||
}
|
||||
|
@ -479,6 +479,7 @@ ul.chatlist {
|
||||
.user-title,
|
||||
.user-last-message {
|
||||
flex-grow: 1;
|
||||
position: relative; // * for custom emoji
|
||||
|
||||
i {
|
||||
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 {
|
||||
--anim: .4s ease;
|
||||
position: relative;
|
||||
// position: relative; // ! idk what it was for
|
||||
background-color: var(--spoiler-background-color);
|
||||
|
||||
&-text {
|
||||
@ -23,24 +23,28 @@
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
.message {
|
||||
&.will-change {
|
||||
.spoiler {
|
||||
// box-shadow: 0 0 var(--spoiler-background-color);
|
||||
|
||||
&-text {
|
||||
filter: blur(6px);
|
||||
}
|
||||
}
|
||||
.spoilers-container {
|
||||
.custom-emoji-canvas {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
// &.will-change {
|
||||
// .spoiler {
|
||||
// // box-shadow: 0 0 var(--spoiler-background-color);
|
||||
|
||||
// &-text {
|
||||
// filter: blur(6px);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
&.is-spoiler-visible {
|
||||
&.animating {
|
||||
.spoiler {
|
||||
transition: /* box-shadow var(--anim), */ background-color var(--anim);
|
||||
|
||||
&-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;
|
||||
|
||||
&-text {
|
||||
filter: blur(0);
|
||||
// filter: blur(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.backwards {
|
||||
.spoiler-text {
|
||||
filter: blur(3px);
|
||||
}
|
||||
}
|
||||
// &.backwards {
|
||||
// .spoiler-text {
|
||||
// filter: blur(3px);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
&:not(.is-spoiler-visible) {
|
||||
|
@ -114,6 +114,8 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||
--call-button-size: 3.375rem;
|
||||
--call-button-margin: 2rem;
|
||||
|
||||
--custom-emoji-size: 1.125rem;
|
||||
|
||||
// https://github.com/overtake/TelegramSwift/blob/5cc7d2475fe4738a6aa0486c23eaf80a89d33b97/submodules/TGUIKit/TGUIKit/PresentationTheme.swift#L2054
|
||||
--peer-avatar-red-top: #ff885e;
|
||||
--peer-avatar-red-bottom: #ff516a;
|
||||
@ -362,6 +364,7 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||
@import "partials/reaction";
|
||||
@import "partials/stackedAvatars";
|
||||
@import "partials/stickerViewer";
|
||||
@import "partials/customEmoji";
|
||||
|
||||
@import "partials/popups/popup";
|
||||
@import "partials/popups/editAvatar";
|
||||
@ -913,12 +916,13 @@ hr {
|
||||
|
||||
span.emoji {
|
||||
display: inline !important;
|
||||
vertical-align: unset !important;
|
||||
//line-height: 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;
|
||||
vertical-align: unset !important;
|
||||
line-height: 1 !important;
|
||||
// vertical-align: text-top !important;
|
||||
}
|
||||
|
||||
// non-Retina device
|
||||
|
Loading…
x
Reference in New Issue
Block a user