Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
8.5 KiB

3 years ago
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {CustomEmojiRendererElement} from '../lib/richTextProcessor/wrapRichText';
2 years ago
import rootScope from '../lib/rootScope';
import {IS_SAFARI} from '../environment/userAgent';
import {MOUNT_CLASS_TO} from '../config/debug';
import isInDOM from '../helpers/dom/isInDOM';
import RLottiePlayer from '../lib/rlottie/rlottiePlayer';
import indexOfAndSplice from '../helpers/array/indexOfAndSplice';
import forEachReverse from '../helpers/array/forEachReverse';
import idleController from '../helpers/idleController';
import appMediaPlaybackController from './appMediaPlaybackController';
2 years ago
import {fastRaf} from '../helpers/schedulers';
3 years ago
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
2 years ago
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS' | 'STICKER-VIEWER' | 'EMOJI';
3 years ago
export interface AnimationItem {
el: HTMLElement,
group: AnimationItemGroup,
animation: AnimationItemWrapper
};
export interface AnimationItemWrapper {
remove: () => void;
paused: boolean;
pause: () => any;
play: () => any;
autoplay: boolean;
// onVisibilityChange?: (visible: boolean) => boolean;
3 years ago
};
export class AnimationIntersector {
2 years ago
private observer: IntersectionObserver;
3 years ago
private visible: Set<AnimationItem> = new Set();
2 years ago
private overrideIdleGroups: Set<string>;
private byGroups: {[group in AnimationItemGroup]?: AnimationItem[]} = {};
private lockedGroups: {[group in AnimationItemGroup]?: true} = {};
private onlyOnePlayableGroup: AnimationItemGroup = '';
2 years ago
private intersectionLockedGroups: {[group in AnimationItemGroup]?: true} = {};
3 years ago
private videosLocked = false;
constructor() {
this.observer = new IntersectionObserver((entries) => {
2 years ago
// if(rootScope.idle.isIDLE) return;
3 years ago
for(const entry of entries) {
const target = entry.target;
for(const group in this.byGroups) {
if(this.intersectionLockedGroups[group as AnimationItemGroup]) {
3 years ago
continue;
}
2 years ago
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();
} */
3 years ago
}
2 years ago
break;
3 years ago
}
}
});
2 years ago
this.overrideIdleGroups = new Set();
appMediaPlaybackController.addEventListener('play', ({doc}) => {
3 years ago
if(doc.type === 'round') {
this.videosLocked = true;
this.checkAnimations();
}
});
appMediaPlaybackController.addEventListener('pause', () => {
3 years ago
if(this.videosLocked) {
this.videosLocked = false;
this.checkAnimations();
}
});
idleController.addEventListener('change', (idle) => {
this.checkAnimations(idle);
});
3 years ago
}
2 years ago
public setOverrideIdleGroup(group: string, override: boolean) {
if(override) this.overrideIdleGroups.add(group);
else this.overrideIdleGroups.delete(group);
}
3 years ago
public getAnimations(element: HTMLElement) {
const found: AnimationItem[] = [];
for(const group in this.byGroups) {
for(const player of this.byGroups[group as AnimationItemGroup]) {
3 years ago
if(player.el === element) {
found.push(player);
}
}
}
return found;
}
public removeAnimation(player: AnimationItem) {
const {el, animation} = player;
animation.remove();
if(animation instanceof HTMLVideoElement && IS_SAFARI) {
3 years ago
setTimeout(() => { // TODO: очистка по очереди, а не все вместе с этим таймаутом
animation.src = '';
animation.load();
}, 1e3);
}
2 years ago
const group = this.byGroups[player.group];
if(group) {
indexOfAndSplice(group, player);
if(!group.length) {
delete this.byGroups[player.group];
}
3 years ago
}
2 years ago
3 years ago
this.observer.unobserve(el);
this.visible.delete(player);
}
2 years ago
public addAnimation(_animation: AnimationItem['animation'], group: AnimationItemGroup = '') {
if(group === 'none') {
return;
}
let el: HTMLElement;
if(_animation instanceof RLottiePlayer) {
el = _animation.el[0];
} else if(_animation instanceof CustomEmojiRendererElement) {
el = _animation.canvas;
} else if(_animation instanceof HTMLElement) {
el = _animation;
}
2 years ago
const animation: AnimationItem = {
el,
2 years ago
animation: _animation,
3 years ago
group
};
2 years ago
if(_animation instanceof RLottiePlayer) {
if(!rootScope.settings.stickers.loop && _animation.loop) {
_animation.loop = rootScope.settings.stickers.loop;
3 years ago
}
}
2 years ago
(this.byGroups[group as AnimationItemGroup] ??= []).push(animation);
this.observer.observe(animation.el);
3 years ago
}
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
2 years ago
// if(rootScope.idle.isIDLE) return;
3 years ago
2 years ago
if(group !== undefined && !this.byGroups[group]) {
2 years ago
// console.warn('no animation group:', group);
3 years ago
return;
}
2 years ago
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups) as AnimationItemGroup[];
3 years ago
for(const group of groups) {
const animations = this.byGroups[group];
2 years ago
forEachReverse(animations, (animation) => {
this.checkAnimation(animation, blurred, destroy);
3 years ago
});
}
}
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
const {el, animation, group} = player;
2 years ago
// return;
2 years ago
if(destroy || (!this.lockedGroups[group] && !isInDOM(el))) {
3 years ago
this.removeAnimation(player);
return;
}
if(blurred ||
(this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group) ||
(animation instanceof HTMLVideoElement && this.videosLocked)
) {
3 years ago
if(!animation.paused) {
2 years ago
// console.warn('pause animation:', animation);
3 years ago
animation.pause();
}
2 years ago
} else if(animation.paused &&
this.visible.has(player) &&
animation.autoplay &&
2 years ago
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group) &&
(!idleController.isIdle || this.overrideIdleGroups.has(player.group))
3 years ago
) {
2 years ago
// console.warn('play animation:', animation);
3 years ago
animation.play();
}
}
2 years ago
public getOnlyOnePlayableGroup() {
return this.onlyOnePlayableGroup;
}
public setOnlyOnePlayableGroup(group: AnimationItemGroup = '') {
3 years ago
this.onlyOnePlayableGroup = group;
}
public lockGroup(group: AnimationItemGroup) {
3 years ago
this.lockedGroups[group] = true;
}
public unlockGroup(group: AnimationItemGroup) {
3 years ago
delete this.lockedGroups[group];
this.checkAnimations(undefined, group);
}
public refreshGroup(group: AnimationItemGroup) {
3 years ago
const animations = this.byGroups[group];
2 years ago
if(!animations?.length) {
return;
}
animations.forEach((animation) => {
this.observer.unobserve(animation.el);
});
3 years ago
2 years ago
fastRaf(() => {
animations.forEach((animation) => {
this.observer.observe(animation.el);
3 years ago
});
2 years ago
});
3 years ago
}
public lockIntersectionGroup(group: AnimationItemGroup) {
3 years ago
this.intersectionLockedGroups[group] = true;
}
public unlockIntersectionGroup(group: AnimationItemGroup) {
3 years ago
delete this.intersectionLockedGroups[group];
this.refreshGroup(group);
}
}
const animationIntersector = new AnimationIntersector();
2 years ago
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.animationIntersector = animationIntersector);
2 years ago
export default animationIntersector;