Sticker viewer
This commit is contained in:
parent
b9e6151d5c
commit
3266d1d4c6
@ -16,7 +16,7 @@ import appMediaPlaybackController from './appMediaPlaybackController';
|
||||
|
||||
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
|
||||
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
|
||||
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS';
|
||||
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS' | 'STICKER-VIEWER';
|
||||
export interface AnimationItem {
|
||||
el: HTMLElement,
|
||||
group: AnimationItemGroup,
|
||||
@ -201,7 +201,11 @@ export class AnimationIntersector {
|
||||
}
|
||||
}
|
||||
|
||||
public setOnlyOnePlayableGroup(group: AnimationItemGroup) {
|
||||
public getOnlyOnePlayableGroup() {
|
||||
return this.onlyOnePlayableGroup;
|
||||
}
|
||||
|
||||
public setOnlyOnePlayableGroup(group: AnimationItemGroup = '') {
|
||||
this.onlyOnePlayableGroup = group;
|
||||
}
|
||||
|
||||
|
@ -111,6 +111,7 @@ import getAlbumText from '../../lib/appManagers/utils/messages/getAlbumText';
|
||||
import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount';
|
||||
import PopupPayment from '../popups/payment';
|
||||
import isInDOM from '../../helpers/dom/isInDOM';
|
||||
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||
|
||||
const USE_MEDIA_TAILS = false;
|
||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||
@ -1234,9 +1235,6 @@ export default class ChatBubbles {
|
||||
needFadeIn: false
|
||||
}).then(({render}) => render).then((player) => {
|
||||
assumeType<RLottiePlayer>(player);
|
||||
if(!middleware()) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.addEventListener('firstFrame', () => {
|
||||
if(!middleware()) {
|
||||
@ -1254,7 +1252,7 @@ export default class ChatBubbles {
|
||||
this.managers.appReactionsManager.sendReaction(message, availableReaction.reaction);
|
||||
this.unhoverPrevious();
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
});
|
||||
}, noop);
|
||||
});
|
||||
} else if(hoverReaction.dataset.loaded) {
|
||||
this.setHoverVisible(hoverReaction, true);
|
||||
@ -4035,7 +4033,7 @@ export default class ChatBubbles {
|
||||
noPremium: messageMedia?.pFlags?.nopremium
|
||||
});
|
||||
|
||||
if(isInUnread || isOutgoing/* || true */) {
|
||||
if(getStickerEffectThumb(doc) && (isInUnread || isOutgoing)/* || true */) {
|
||||
this.observer.observe(bubble, this.stickerEffectObserverCallback);
|
||||
}
|
||||
} else if(doc.type === 'video' || doc.type === 'gif' || doc.type === 'round'/* && doc.size <= 20e6 */) {
|
||||
|
@ -33,7 +33,7 @@ export default class PopupStickers extends PopupElement {
|
||||
this.title.append(i18n('Loading'));
|
||||
|
||||
this.addEventListener('close', () => {
|
||||
animationIntersector.setOnlyOnePlayableGroup('');
|
||||
animationIntersector.setOnlyOnePlayableGroup();
|
||||
});
|
||||
|
||||
const div = document.createElement('div');
|
||||
|
@ -12,7 +12,8 @@ const SetTransition = (
|
||||
forwards: boolean,
|
||||
duration: number,
|
||||
onTransitionEnd?: () => void,
|
||||
useRafs?: number
|
||||
useRafs?: number,
|
||||
onTransitionStart?: () => void
|
||||
) => {
|
||||
const {timeout, raf} = element.dataset;
|
||||
if(timeout !== undefined) {
|
||||
@ -36,7 +37,7 @@ const SetTransition = (
|
||||
if(useRafs && rootScope.settings.animationsEnabled && duration) {
|
||||
element.dataset.raf = '' + window.requestAnimationFrame(() => {
|
||||
delete element.dataset.raf;
|
||||
SetTransition(element, className, forwards, duration, onTransitionEnd, useRafs - 1);
|
||||
SetTransition(element, className, forwards, duration, onTransitionEnd, useRafs - 1, onTransitionStart);
|
||||
});
|
||||
|
||||
return;
|
||||
@ -54,9 +55,10 @@ const SetTransition = (
|
||||
|
||||
element.classList.remove('animating');
|
||||
|
||||
onTransitionEnd && onTransitionEnd();
|
||||
onTransitionEnd?.();
|
||||
};
|
||||
|
||||
onTransitionStart?.();
|
||||
if(!rootScope.settings.animationsEnabled || !duration) {
|
||||
element.classList.remove('animating', 'backwards');
|
||||
afterTimeout();
|
||||
|
@ -16,6 +16,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName';
|
||||
import renderImageFromUrl from '../../helpers/dom/renderImageFromUrl';
|
||||
import getImageFromStrippedThumb from '../../helpers/getImageFromStrippedThumb';
|
||||
import getPreviewURLFromThumb from '../../helpers/getPreviewURLFromThumb';
|
||||
import makeError from '../../helpers/makeError';
|
||||
import onMediaLoad from '../../helpers/onMediaLoad';
|
||||
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
|
||||
import throttle from '../../helpers/schedulers/throttle';
|
||||
@ -33,7 +34,7 @@ import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import type {ThumbCache} from '../../lib/storages/thumbs';
|
||||
import webpWorkerController from '../../lib/webp/webpWorkerController';
|
||||
import {SendMessageEmojiInteractionData} from '../../types';
|
||||
import {Awaited, SendMessageEmojiInteractionData} from '../../types';
|
||||
import {getEmojiToneIndex} from '../../vendor/emoji';
|
||||
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
|
||||
import LazyLoadQueue from '../lazyLoadQueue';
|
||||
@ -42,12 +43,12 @@ import {hideToast, toastNew} from '../toast';
|
||||
import wrapStickerAnimation from './stickerAnimation';
|
||||
|
||||
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
|
||||
const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
|
||||
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
|
||||
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}: {
|
||||
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}: {
|
||||
doc: MyDocument,
|
||||
div: HTMLElement,
|
||||
middleware?: () => boolean,
|
||||
@ -69,7 +70,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
fullThumb?: PhotoSize | VideoSize,
|
||||
isOut?: boolean,
|
||||
noPremium?: boolean,
|
||||
withLock?: boolean
|
||||
withLock?: boolean,
|
||||
relativeEffect?: boolean,
|
||||
loopEffect?: boolean
|
||||
}) {
|
||||
const stickerType = doc.sticker;
|
||||
if(stickerType === 1) {
|
||||
@ -305,8 +308,11 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
return;
|
||||
}
|
||||
|
||||
const middlewareError = makeError('MIDDLEWARE');
|
||||
const load = async() => {
|
||||
if(middleware && !middleware()) return;
|
||||
if(middleware && !middleware()) {
|
||||
throw middlewareError;
|
||||
}
|
||||
|
||||
if(stickerType === 2 && !asStatic) {
|
||||
/* if(doc.id === '1860749763008266301') {
|
||||
@ -325,7 +331,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
// console.timeEnd('download sticker' + doc.id);
|
||||
// console.log('loaded sticker:', doc, div/* , blob */);
|
||||
if(middleware && !middleware()) {
|
||||
throw new Error('wrapSticker 2 middleware');
|
||||
throw middlewareError;
|
||||
}
|
||||
|
||||
const animation = await lottieLoader.loadAnimationWorker({
|
||||
@ -355,7 +361,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
}
|
||||
|
||||
const cb = () => {
|
||||
if(element && element !== animation.canvas) {
|
||||
if(element && element !== animation.canvas && element.tagName !== 'DIV') {
|
||||
element.remove();
|
||||
}
|
||||
};
|
||||
@ -513,6 +519,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
|
||||
if(play) {
|
||||
(media as HTMLVideoElement).autoplay = true;
|
||||
}
|
||||
|
||||
if(loop) {
|
||||
(media as HTMLVideoElement).loop = true;
|
||||
}
|
||||
}
|
||||
@ -528,16 +537,17 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
media.classList.add('fade-in');
|
||||
}
|
||||
|
||||
return new Promise<void>(async(resolve, reject) => {
|
||||
return new Promise<HTMLVideoElement | HTMLImageElement>(async(resolve, reject) => {
|
||||
const r = async() => {
|
||||
if(middleware && !middleware()) return resolve();
|
||||
if(middleware && !middleware()) {
|
||||
reject(middlewareError);
|
||||
return;
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
sequentialDom.mutateElement(div, () => {
|
||||
div.append(media);
|
||||
if(thumbImage) {
|
||||
thumbImage.classList.add('fade-out');
|
||||
}
|
||||
thumbImage && thumbImage.classList.add('fade-out');
|
||||
|
||||
if(stickerType === 3 && !isSavingLottiePreview(doc, toneIndex)) {
|
||||
// const perf = performance.now();
|
||||
@ -555,14 +565,12 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
animationIntersector.addAnimation(media as HTMLVideoElement, group);
|
||||
}
|
||||
|
||||
resolve();
|
||||
resolve(media as any);
|
||||
|
||||
if(needFadeIn) {
|
||||
media.addEventListener('animationend', () => {
|
||||
media.classList.remove('fade-in');
|
||||
if(thumbImage) {
|
||||
thumbImage.remove();
|
||||
}
|
||||
thumbImage?.remove();
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
@ -589,13 +597,13 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
promise = appDownloadManager.downloadMediaURL({media: doc, queueId: lazyLoadQueue?.queueId});
|
||||
}
|
||||
|
||||
promise.then(r, resolve);
|
||||
promise.then(r, reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const loadPromise: Promise<RLottiePlayer | void> = lazyLoadQueue && (!downloaded || isAnimated) ?
|
||||
const loadPromise: Promise<Awaited<ReturnType<typeof load>> | void> = lazyLoadQueue && (!downloaded || isAnimated) ?
|
||||
(lazyLoadQueue.push({div, load}), Promise.resolve()) :
|
||||
load();
|
||||
|
||||
@ -614,21 +622,25 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
|
||||
middleware,
|
||||
isOut,
|
||||
width,
|
||||
loadPromise
|
||||
loadPromise,
|
||||
relativeEffect,
|
||||
loopEffect
|
||||
});
|
||||
}
|
||||
|
||||
return {render: loadPromise};
|
||||
}
|
||||
|
||||
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise}: {
|
||||
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise, relativeEffect, loopEffect}: {
|
||||
container: HTMLElement,
|
||||
doc: MyDocument,
|
||||
managers: AppManagers,
|
||||
middleware: () => boolean,
|
||||
isOut: boolean,
|
||||
width: number,
|
||||
loadPromise: Promise<any>
|
||||
loadPromise: Promise<any>,
|
||||
relativeEffect?: boolean,
|
||||
loopEffect?: boolean
|
||||
}) {
|
||||
managers.appStickersManager.preloadSticker(doc.id, true);
|
||||
|
||||
@ -660,10 +672,12 @@ function attachStickerEffectHandler({container, doc, managers, middleware, isOut
|
||||
size: width * STICKER_EFFECT_MULTIPLIER,
|
||||
target: container,
|
||||
play: true,
|
||||
fullThumb: getStickerEffectThumb(doc)
|
||||
fullThumb: getStickerEffectThumb(doc),
|
||||
relativeEffect,
|
||||
loopEffect
|
||||
});
|
||||
|
||||
if(isOut !== undefined && !isOut) {
|
||||
if(isOut !== undefined && !isOut/* && !relativeEffect */) {
|
||||
animationDiv.classList.add('reflect-x');
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,9 @@ export default function wrapStickerAnimation({
|
||||
play,
|
||||
managers,
|
||||
fullThumb,
|
||||
withRandomOffset
|
||||
withRandomOffset,
|
||||
relativeEffect,
|
||||
loopEffect
|
||||
}: {
|
||||
size: number,
|
||||
doc: MyDocument,
|
||||
@ -37,7 +39,9 @@ export default function wrapStickerAnimation({
|
||||
play: boolean,
|
||||
managers?: AppManagers,
|
||||
fullThumb?: PhotoSize | VideoSize,
|
||||
withRandomOffset?: boolean
|
||||
withRandomOffset?: boolean,
|
||||
relativeEffect?: boolean,
|
||||
loopEffect?: boolean
|
||||
}) {
|
||||
const animationDiv = document.createElement('div');
|
||||
animationDiv.classList.add('emoji-animation');
|
||||
@ -59,7 +63,7 @@ export default function wrapStickerAnimation({
|
||||
middleware,
|
||||
withThumb: false,
|
||||
needFadeIn: false,
|
||||
loop: false,
|
||||
loop: !!loopEffect,
|
||||
width: size,
|
||||
height: size,
|
||||
play,
|
||||
@ -71,7 +75,7 @@ export default function wrapStickerAnimation({
|
||||
assumeType<RLottiePlayer>(_animation);
|
||||
animation = _animation;
|
||||
animation.addEventListener('enterFrame', (frameNo) => {
|
||||
if(frameNo === animation.maxFrame || !isInDOM(target)) {
|
||||
if((!loopEffect && frameNo === animation.maxFrame) || !isInDOM(target)) {
|
||||
unmountAnimation();
|
||||
}
|
||||
});
|
||||
@ -92,7 +96,6 @@ export default function wrapStickerAnimation({
|
||||
|
||||
const randomOffsetX = withRandomOffset ? generateRandomSigned(16) : 0;
|
||||
const randomOffsetY = withRandomOffset ? generateRandomSigned(4) : 0;
|
||||
const stableOffsetX = /* size / 8 */16 * (side === 'right' ? 1 : -1);
|
||||
const setPosition = () => {
|
||||
if(!isInDOM(target)) {
|
||||
unmountAnimation();
|
||||
@ -100,35 +103,46 @@ export default function wrapStickerAnimation({
|
||||
}
|
||||
|
||||
const rect = target.getBoundingClientRect();
|
||||
/* const boxWidth = Math.max(rect.width, rect.height);
|
||||
const boxHeight = Math.max(rect.width, rect.height);
|
||||
const x = rect.left + ((boxWidth - size) / 2);
|
||||
const y = rect.top + ((boxHeight - size) / 2); */
|
||||
|
||||
const factor = rect.width / 200;
|
||||
const stableOffsetX = side === 'center' ? 0 : 16 * (side === 'right' ? 1 : -1) * factor;
|
||||
// const stableOffsetY = side === 'center' ? 0 : -50 * factor;
|
||||
const stableOffsetY = side === 'center' ? 0 : 0 * factor;
|
||||
|
||||
const rectX = side === 'right' ? rect.right : rect.left;
|
||||
const rectY = rect.top;
|
||||
|
||||
const addOffsetX = side === 'center' ? (rect.width - size) / 2 : (side === 'right' ? -size : 0) + stableOffsetX + randomOffsetX;
|
||||
const addOffsetX = (side === 'center' ? (rect.width - size) / 2 : (side === 'right' ? -size : 0)) + stableOffsetX + randomOffsetX;
|
||||
const addOffsetY = (side === 'center' || true ? (rect.height - size) / 2 : 0) + stableOffsetY + randomOffsetY;
|
||||
const x = rectX + addOffsetX;
|
||||
// const y = rect.bottom - size + size / 4;
|
||||
const y = rect.top + ((rect.height - size) / 2) + (side === 'center' ? 0 : randomOffsetY);
|
||||
// animationDiv.style.transform = `translate(${x}px, ${y}px)`;
|
||||
const y = rectY + addOffsetY;
|
||||
|
||||
if(y <= -size || y >= windowSize.height) {
|
||||
unmountAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
animationDiv.style.top = y + 'px';
|
||||
animationDiv.style.left = x + 'px';
|
||||
if(relativeEffect) {
|
||||
if(side !== 'center') animationDiv.style[side] = Math.abs(stableOffsetX) * -1 + 'px';
|
||||
else animationDiv.style.left = addOffsetX + 'px';
|
||||
animationDiv.style.top = addOffsetY + 'px';
|
||||
} else {
|
||||
animationDiv.style.top = y + 'px';
|
||||
animationDiv.style.left = x + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
const onScroll = throttleWithRaf(setPosition);
|
||||
|
||||
appImManager.chat.bubbles.scrollable.container.addEventListener('scroll', onScroll);
|
||||
|
||||
setPosition();
|
||||
|
||||
appImManager.emojiAnimationContainer.append(animationDiv);
|
||||
if(relativeEffect) {
|
||||
animationDiv.classList.add('is-relative');
|
||||
target.parentElement.append(animationDiv);
|
||||
} else {
|
||||
appImManager.emojiAnimationContainer.append(animationDiv);
|
||||
}
|
||||
|
||||
return {animationDiv, stickerPromise};
|
||||
}
|
||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -30,7 +30,7 @@ declare global {
|
||||
type FiltersError = 'PINNED_DIALOGS_TOO_MUCH';
|
||||
|
||||
type LocalFileError = ApiFileManagerError | ReferenceError | StorageError;
|
||||
type LocalErrorType = LocalFileError | NetworkerError | FiltersError | 'UNKNOWN' | 'NO_DOC';
|
||||
type LocalErrorType = LocalFileError | NetworkerError | FiltersError | 'UNKNOWN' | 'NO_DOC' | 'MIDDLEWARE';
|
||||
|
||||
type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' |
|
||||
'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' |
|
||||
|
@ -55,7 +55,7 @@ import {CallType} from '../calls/types';
|
||||
import {Modify, SendMessageEmojiInteractionData} from '../../types';
|
||||
import htmlToSpan from '../../helpers/dom/htmlToSpan';
|
||||
import getVisibleRect from '../../helpers/dom/getVisibleRect';
|
||||
import {simulateClickEvent} from '../../helpers/dom/clickEvent';
|
||||
import {attachClickEvent, simulateClickEvent} from '../../helpers/dom/clickEvent';
|
||||
import PopupCall from '../../components/call';
|
||||
import copy from '../../helpers/object/copy';
|
||||
import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort';
|
||||
@ -92,6 +92,15 @@ 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';
|
||||
|
||||
@ -207,15 +216,17 @@ export class AppImManager extends EventListenerBase<{
|
||||
this.setSettings();
|
||||
rootScope.addEventListener('settings_updated', this.setSettings);
|
||||
|
||||
rootScope.addEventListener('premium_toggle', (isPremium) => {
|
||||
const onPremiumToggle = (isPremium: boolean) => {
|
||||
document.body.classList.toggle('is-premium', isPremium);
|
||||
});
|
||||
};
|
||||
rootScope.addEventListener('premium_toggle', onPremiumToggle);
|
||||
onPremiumToggle(rootScope.premium);
|
||||
|
||||
useHeavyAnimationCheck(() => {
|
||||
animationIntersector.setOnlyOnePlayableGroup('lock');
|
||||
animationIntersector.checkAnimations(true);
|
||||
}, () => {
|
||||
animationIntersector.setOnlyOnePlayableGroup('');
|
||||
animationIntersector.setOnlyOnePlayableGroup();
|
||||
animationIntersector.checkAnimations(false);
|
||||
});
|
||||
|
||||
@ -404,6 +415,251 @@ export class AppImManager extends EventListenerBase<{
|
||||
}, useRafs);
|
||||
};
|
||||
|
||||
let hasViewer = false;
|
||||
!IS_TOUCH_SUPPORTED && document.addEventListener('mousedown', (e) => {
|
||||
if(hasViewer || e.buttons > 1 || e.button !== 0) return;
|
||||
let mediaContainer = findUpClassName(e.target, 'media-sticker-wrapper');
|
||||
if(!mediaContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// const img: HTMLImageElement = mediaContainer.querySelector('img.media-sticker');
|
||||
|
||||
const docId = mediaContainer.dataset.docId;
|
||||
if(!docId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const className = 'sticker-viewer';
|
||||
const group: AnimationItemGroup = 'STICKER-VIEWER';
|
||||
const openDuration = 200;
|
||||
const switchDuration = 200;
|
||||
const previousGroup = animationIntersector.getOnlyOnePlayableGroup();
|
||||
const _middleware = getMiddleware();
|
||||
let container: HTMLElement, previousTransformer: HTMLElement;
|
||||
|
||||
const doThatSticker = async({mediaContainer, doc, middleware, lockGroups, isSwitching}: {
|
||||
mediaContainer: HTMLElement,
|
||||
doc: MyDocument,
|
||||
middleware: () => boolean,
|
||||
lockGroups?: boolean,
|
||||
isSwitching?: boolean
|
||||
}) => {
|
||||
const effectThumb = getStickerEffectThumb(doc);
|
||||
const mediaRect: DOMRect = mediaContainer.getBoundingClientRect();
|
||||
const s = makeMediaSize(doc.w, doc.h);
|
||||
const size = effectThumb ? 280 : 360;
|
||||
const boxSize = makeMediaSize(size, size);
|
||||
const fitted = mediaRect.width === mediaRect.height ? boxSize : s.aspectFitted(boxSize);
|
||||
|
||||
const bubble = findUpClassName(mediaContainer, 'bubble');
|
||||
const isOut = bubble ? bubble.classList.contains('is-out') : true;
|
||||
|
||||
const transformer = document.createElement('div');
|
||||
transformer.classList.add(className + '-transformer');
|
||||
|
||||
const stickerContainer = document.createElement('div');
|
||||
stickerContainer.classList.add(className + '-sticker');
|
||||
/* transformer.style.width = */stickerContainer.style.width = fitted.width + 'px';
|
||||
/* transformer.style.height = */stickerContainer.style.height = fitted.height + 'px';
|
||||
|
||||
const stickerEmoji = document.createElement('div');
|
||||
stickerEmoji.classList.add(className + '-emoji');
|
||||
stickerEmoji.append(wrapEmojiText(doc.stickerEmojiRaw));
|
||||
|
||||
if(effectThumb) {
|
||||
const margin = (size * STICKER_EFFECT_MULTIPLIER - size) / 3 * (isOut ? 1 : -1);
|
||||
transformer.classList.add('has-effect');
|
||||
// const property = `--margin-${isOut ? 'right' : 'left'}`;
|
||||
// stickerContainer.style.setProperty(property, `${margin * 2}px`);
|
||||
transformer.style.setProperty('--translateX', `${margin}px`);
|
||||
stickerEmoji.style.setProperty('--translateX', `${-margin}px`);
|
||||
}
|
||||
|
||||
const overflowElement = findUpClassName(mediaContainer, 'scrollable');
|
||||
const visibleRect = getVisibleRect(mediaContainer, overflowElement, true, mediaRect);
|
||||
if(visibleRect.overflow.vertical || visibleRect.overflow.horizontal) {
|
||||
stickerContainer.classList.add('is-overflow');
|
||||
}
|
||||
|
||||
// if(img) {
|
||||
// const ratio = img.naturalWidth / img.naturalHeight;
|
||||
// if((mediaRect.width / mediaRect.height).toFixed(1) !== ratio.toFixed(1)) {
|
||||
|
||||
// mediaRect = mediaRect.toJSON();
|
||||
// }
|
||||
// }
|
||||
|
||||
const rect = mediaContainer.getBoundingClientRect();
|
||||
const scaleX = rect.width / fitted.width;
|
||||
const scaleY = rect.height / fitted.height;
|
||||
const transformX = rect.left - (windowSize.width - rect.width) / 2;
|
||||
const transformY = rect.top - (windowSize.height - rect.height) / 2;
|
||||
transformer.style.transform = `translate(${transformX}px, ${transformY}px) scale(${scaleX}, ${scaleY})`;
|
||||
if(isSwitching) transformer.classList.add('is-switching');
|
||||
transformer.append(stickerContainer, stickerEmoji);
|
||||
container.append(transformer);
|
||||
|
||||
const player = await wrapSticker({
|
||||
doc,
|
||||
div: stickerContainer,
|
||||
group,
|
||||
width: fitted.width,
|
||||
height: fitted.height,
|
||||
play: false,
|
||||
loop: true,
|
||||
middleware,
|
||||
managers: this.managers,
|
||||
needFadeIn: false,
|
||||
isOut,
|
||||
withThumb: false,
|
||||
relativeEffect: true,
|
||||
loopEffect: true
|
||||
}).then(({render}) => render);
|
||||
if(!middleware()) return;
|
||||
|
||||
if(!container.parentElement) {
|
||||
document.body.append(container);
|
||||
}
|
||||
|
||||
const firstFramePromise = player instanceof RLottiePlayer ?
|
||||
new Promise<void>((resolve) => player.addEventListener('firstFrame', resolve, {once: true})) :
|
||||
Promise.resolve();
|
||||
await Promise.all([firstFramePromise, doubleRaf()]);
|
||||
await pause(0); // ! need it because firstFrame will be called just from the loop
|
||||
if(!middleware()) return;
|
||||
|
||||
if(lockGroups) {
|
||||
animationIntersector.setOnlyOnePlayableGroup(group);
|
||||
animationIntersector.checkAnimations(true);
|
||||
}
|
||||
|
||||
if(player instanceof RLottiePlayer) {
|
||||
const prevPlayer = lottieLoader.getAnimation(mediaContainer);
|
||||
player.curFrame = prevPlayer.curFrame;
|
||||
player.play();
|
||||
await new Promise<void>((resolve) => {
|
||||
let i = 0;
|
||||
const c = () => {
|
||||
if(++i === 2) {
|
||||
resolve();
|
||||
player.removeEventListener('enterFrame', c);
|
||||
}
|
||||
};
|
||||
|
||||
player.addEventListener('enterFrame', c);
|
||||
});
|
||||
player.pause();
|
||||
} else if(player instanceof HTMLVideoElement) {
|
||||
player.currentTime = (mediaContainer.querySelector('video') as HTMLVideoElement).currentTime;
|
||||
}
|
||||
|
||||
return {
|
||||
ready: () => {
|
||||
if(player instanceof RLottiePlayer || player instanceof HTMLVideoElement) {
|
||||
player.play();
|
||||
}
|
||||
|
||||
if(effectThumb) {
|
||||
simulateClickEvent(stickerContainer);
|
||||
}
|
||||
},
|
||||
transformer
|
||||
};
|
||||
};
|
||||
|
||||
const timeout = window.setTimeout(async() => {
|
||||
document.removeEventListener('mousemove', onMousePreMove);
|
||||
|
||||
container = document.createElement('div');
|
||||
container.classList.add(className);
|
||||
hasViewer = true;
|
||||
|
||||
const middleware = _middleware.get();
|
||||
const doc = await this.managers.appDocsManager.getDoc(docId);
|
||||
if(!middleware()) return;
|
||||
|
||||
const {ready, transformer} = await doThatSticker({
|
||||
doc,
|
||||
mediaContainer,
|
||||
middleware,
|
||||
lockGroups: true
|
||||
});
|
||||
|
||||
previousTransformer = transformer;
|
||||
|
||||
SetTransition(container, 'is-visible', true, openDuration, () => {
|
||||
if(!middleware()) return;
|
||||
ready();
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
}, 100);
|
||||
|
||||
const onMouseMove = async(e: MouseEvent) => {
|
||||
const newMediaContainer = findUpClassName(e.target, 'media-sticker-wrapper');
|
||||
if(!newMediaContainer || mediaContainer === newMediaContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docId = newMediaContainer.dataset.docId;
|
||||
if(!docId) {
|
||||
return;
|
||||
}
|
||||
|
||||
mediaContainer = newMediaContainer;
|
||||
_middleware.clean();
|
||||
const middleware = _middleware.get();
|
||||
|
||||
const doc = await this.managers.appDocsManager.getDoc(docId);
|
||||
if(!middleware()) return;
|
||||
|
||||
const {ready, transformer} = await doThatSticker({
|
||||
doc,
|
||||
mediaContainer,
|
||||
middleware,
|
||||
isSwitching: true
|
||||
});
|
||||
|
||||
const _previousTransformer = previousTransformer;
|
||||
SetTransition(_previousTransformer, 'is-switching', true, switchDuration, () => {
|
||||
_previousTransformer.remove();
|
||||
});
|
||||
|
||||
previousTransformer = transformer;
|
||||
|
||||
SetTransition(transformer, 'is-switching', false, switchDuration, () => {
|
||||
if(!middleware()) return;
|
||||
ready();
|
||||
});
|
||||
};
|
||||
|
||||
const onMousePreMove = () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
clearTimeout(timeout);
|
||||
_middleware.clean();
|
||||
|
||||
if(container) {
|
||||
SetTransition(container, 'is-visible', false, openDuration, () => {
|
||||
container.remove();
|
||||
animationIntersector.setOnlyOnePlayableGroup(previousGroup);
|
||||
animationIntersector.checkAnimations(false);
|
||||
hasViewer = false;
|
||||
});
|
||||
|
||||
attachClickEvent(document.body, cancelEvent, {capture: true, once: true});
|
||||
}
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMousePreMove, {once: true});
|
||||
document.addEventListener('mouseup', onMouseUp, {once: true});
|
||||
});
|
||||
|
||||
apiManagerProxy.addEventListener('notificationBuild', (options) => {
|
||||
if(this.chat.peerId === options.message.peerId && !idleController.isIdle) {
|
||||
return;
|
||||
|
133
src/scss/partials/_stickerViewer.scss
Normal file
133
src/scss/partials/_stickerViewer.scss
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
.sticker-viewer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
opacity: 0;
|
||||
content: " ";
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--sticker-viewer-open-transition-out);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
&:not(.backwards) {
|
||||
&:before {
|
||||
opacity: 1;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--sticker-viewer-open-transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
.sticker-viewer-transformer:not(.is-switching) {
|
||||
transform: translateX(var(--translateX)) scale(1, 1) !important;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform var(--sticker-viewer-open-transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
.sticker-viewer-emoji,
|
||||
.sticker-viewer-sticker,
|
||||
.emoji-animation {
|
||||
opacity: 1;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--sticker-viewer-open-transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
.sticker-viewer-sticker:not(.is-overflow) {
|
||||
@include animation-level(2) {
|
||||
transition: opacity 0s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-transformer {
|
||||
--translateX: 0;
|
||||
position: absolute;
|
||||
// transform: translateX(0) scale(1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
|
||||
&.has-effect {
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
|
||||
.sticker-viewer-emoji {
|
||||
top: -5.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform var(--sticker-viewer-open-transition-out);
|
||||
}
|
||||
|
||||
&.is-switching {
|
||||
transform: translateX(var(--translateX)) scale(1) !important;
|
||||
opacity: 1 !important;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform var(--sticker-viewer-switch-transition), opacity var(--sticker-viewer-switch-transition) !important;
|
||||
}
|
||||
|
||||
&:not(.backwards) {
|
||||
transform: scale(0.4) translateX(var(--translateX)) !important;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-emoji {
|
||||
position: absolute;
|
||||
top: -3rem;
|
||||
transform: translateX(var(--translateX)) scale(2);
|
||||
}
|
||||
|
||||
&-sticker {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// &-sticker,
|
||||
// .emoji-animation {
|
||||
// margin-left: var(--margin-left);
|
||||
// margin-right: var(--margin-right);
|
||||
// }
|
||||
|
||||
&-emoji,
|
||||
&-sticker,
|
||||
.emoji-animation {
|
||||
opacity: 0;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--sticker-viewer-open-transition-out);
|
||||
}
|
||||
}
|
||||
}
|
@ -68,6 +68,10 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||
--btn-menu-transition: .2s cubic-bezier(.4, 0, .2, 1);
|
||||
--esg-transition: var(--btn-menu-transition);
|
||||
--input-transition: .2s ease-out;
|
||||
--sticker-viewer-open-transition-in: .2s var(--transition-standard-easing);
|
||||
--sticker-viewer-open-transition-out: .2s var(--transition-standard-easing);
|
||||
// --sticker-viewer-switch-transition: .2s cubic-bezier(.07,1.21,.56,1.2);
|
||||
--sticker-viewer-switch-transition: .2s cubic-bezier(.12,1.1,.56,1.2);
|
||||
--popup-transition-function: cubic-bezier(.4, 0, .2, 1);
|
||||
--popup-transition-time: .15s;
|
||||
//--layer-transition: .3s cubic-bezier(.33, 1, .68, 1);
|
||||
@ -357,6 +361,7 @@ $chat-input-inner-padding-handhelds: .25rem;
|
||||
@import "partials/reactions";
|
||||
@import "partials/reaction";
|
||||
@import "partials/stackedAvatars";
|
||||
@import "partials/stickerViewer";
|
||||
|
||||
@import "partials/popups/popup";
|
||||
@import "partials/popups/editAvatar";
|
||||
|
4
src/types.d.ts
vendored
4
src/types.d.ts
vendored
@ -84,6 +84,10 @@ type ModifyFunctionsToAsync<T> = {
|
||||
[key in keyof T]: T[key] extends (...args: infer A) => infer R ? (R extends PromiseLike<infer O> ? T[key] : (...args: A) => Promise<Awaited<R>>) : T[key]
|
||||
};
|
||||
|
||||
export type Mutable<T> = {
|
||||
-readonly [K in keyof T]: T[K];
|
||||
};
|
||||
|
||||
export type AuthState = AuthState.signIn | AuthState.signQr | AuthState.authCode | AuthState.password | AuthState.signUp | AuthState.signedIn;
|
||||
export namespace AuthState {
|
||||
export type signIn = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user