Eduard Kuzmenko
2 years ago
19 changed files with 418 additions and 338 deletions
@ -0,0 +1,303 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import IS_TOUCH_SUPPORTED from '../environment/touchSupport'; |
||||||
|
import cancelEvent from '../helpers/dom/cancelEvent'; |
||||||
|
import {simulateClickEvent, attachClickEvent} from '../helpers/dom/clickEvent'; |
||||||
|
import findUpClassName from '../helpers/dom/findUpClassName'; |
||||||
|
import getVisibleRect from '../helpers/dom/getVisibleRect'; |
||||||
|
import ListenerSetter from '../helpers/listenerSetter'; |
||||||
|
import {makeMediaSize} from '../helpers/mediaSize'; |
||||||
|
import {getMiddleware} from '../helpers/middleware'; |
||||||
|
import {doubleRaf} from '../helpers/schedulers'; |
||||||
|
import pause from '../helpers/schedulers/pause'; |
||||||
|
import windowSize from '../helpers/windowSize'; |
||||||
|
import {MyDocument} from '../lib/appManagers/appDocsManager'; |
||||||
|
import getStickerEffectThumb from '../lib/appManagers/utils/stickers/getStickerEffectThumb'; |
||||||
|
import wrapEmojiText from '../lib/richTextProcessor/wrapEmojiText'; |
||||||
|
import lottieLoader from '../lib/rlottie/lottieLoader'; |
||||||
|
import RLottiePlayer from '../lib/rlottie/rlottiePlayer'; |
||||||
|
import rootScope from '../lib/rootScope'; |
||||||
|
import animationIntersector, {AnimationItemGroup} from './animationIntersector'; |
||||||
|
import SetTransition from './singleTransition'; |
||||||
|
import {wrapSticker} from './wrappers'; |
||||||
|
import {STICKER_EFFECT_MULTIPLIER} from './wrappers/sticker'; |
||||||
|
|
||||||
|
let hasViewer = false; |
||||||
|
export default function attachStickerViewerListeners({listenTo, listenerSetter}: { |
||||||
|
listenerSetter: ListenerSetter, |
||||||
|
listenTo: HTMLElement |
||||||
|
}) { |
||||||
|
if(IS_TOUCH_SUPPORTED) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const managers = rootScope.managers; |
||||||
|
const findClassName = 'media-sticker-wrapper'; |
||||||
|
|
||||||
|
listenerSetter.add(listenTo)('mousedown', (e) => { |
||||||
|
if(hasViewer || e.buttons > 1 || e.button !== 0) return; |
||||||
|
let mediaContainer = findUpClassName(e.target, findClassName); |
||||||
|
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, |
||||||
|
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 managers.appDocsManager.getDoc(docId); |
||||||
|
if(!middleware()) return; |
||||||
|
|
||||||
|
let result: Awaited<ReturnType<typeof doThatSticker>>; |
||||||
|
try { |
||||||
|
result = await doThatSticker({ |
||||||
|
doc, |
||||||
|
mediaContainer, |
||||||
|
middleware, |
||||||
|
lockGroups: true |
||||||
|
}); |
||||||
|
if(!result) return; |
||||||
|
} catch(err) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const {ready, transformer} = result; |
||||||
|
|
||||||
|
previousTransformer = transformer; |
||||||
|
|
||||||
|
SetTransition(container, 'is-visible', true, openDuration, () => { |
||||||
|
if(!middleware()) return; |
||||||
|
ready(); |
||||||
|
}); |
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMouseMove); |
||||||
|
}, 125); |
||||||
|
|
||||||
|
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 managers.appDocsManager.getDoc(docId); |
||||||
|
if(!middleware()) return; |
||||||
|
|
||||||
|
let r: Awaited<ReturnType<typeof doThatSticker>>; |
||||||
|
try { |
||||||
|
r = await doThatSticker({ |
||||||
|
doc, |
||||||
|
mediaContainer, |
||||||
|
middleware, |
||||||
|
isSwitching: true |
||||||
|
}); |
||||||
|
if(!r) return; |
||||||
|
} catch(err) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const {ready, transformer} = r; |
||||||
|
|
||||||
|
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 = (e: MouseEvent) => { |
||||||
|
if(!findUpClassName(e.target, findClassName)) { |
||||||
|
onMouseUp(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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', onMousePreMove); |
||||||
|
document.removeEventListener('mousemove', onMouseMove); |
||||||
|
}; |
||||||
|
|
||||||
|
document.addEventListener('mousemove', onMousePreMove); |
||||||
|
document.addEventListener('mouseup', onMouseUp, {once: true}); |
||||||
|
}); |
||||||
|
} |
Loading…
Reference in new issue