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.
291 lines
9.0 KiB
291 lines
9.0 KiB
3 years ago
|
/*
|
||
|
* 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 { IS_SAFARI } from "../../environment/userAgent";
|
||
|
import assumeType from "../../helpers/assumeType";
|
||
|
import callbackify from "../../helpers/callbackify";
|
||
|
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||
|
import findUpClassName from "../../helpers/dom/findUpClassName";
|
||
|
import getVisibleRect from "../../helpers/dom/getVisibleRect";
|
||
|
import { getMiddleware } from "../../helpers/middleware";
|
||
|
import noop from "../../helpers/noop";
|
||
|
import { fastRaf } from "../../helpers/schedulers";
|
||
|
import { Message, AvailableReaction } from "../../layer";
|
||
|
import type { AppReactionsManager } from "../../lib/appManagers/appReactionsManager";
|
||
|
import lottieLoader from "../../lib/rlottie/lottieLoader";
|
||
|
import RLottiePlayer from "../../lib/rlottie/rlottiePlayer";
|
||
|
import rootScope from "../../lib/rootScope";
|
||
|
import animationIntersector from "../animationIntersector";
|
||
|
import Scrollable, { ScrollableBase, ScrollableX } from "../scrollable";
|
||
|
import { wrapSticker } from "../wrappers";
|
||
|
|
||
|
const REACTIONS_CLASS_NAME = 'btn-menu-reactions';
|
||
|
const REACTION_CLASS_NAME = REACTIONS_CLASS_NAME + '-reaction';
|
||
|
|
||
|
const REACTION_SIZE = 28;
|
||
|
const PADDING = 4;
|
||
|
const REACTION_CONTAINER_SIZE = REACTION_SIZE + PADDING * 2;
|
||
|
|
||
|
const CAN_USE_TRANSFORM = !IS_SAFARI;
|
||
|
|
||
|
type ChatReactionsMenuPlayers = {
|
||
|
select?: RLottiePlayer,
|
||
|
appear?: RLottiePlayer,
|
||
|
selectWrapper: HTMLElement,
|
||
|
appearWrapper: HTMLElement,
|
||
|
reaction: string
|
||
|
};
|
||
|
export class ChatReactionsMenu {
|
||
|
public widthContainer: HTMLElement;
|
||
|
private container: HTMLElement;
|
||
|
private reactionsMap: Map<HTMLElement, ChatReactionsMenuPlayers>;
|
||
|
private scrollable: ScrollableBase;
|
||
|
private animationGroup: string;
|
||
|
private middleware: ReturnType<typeof getMiddleware>;
|
||
|
private message: Message.message;
|
||
|
|
||
|
constructor(
|
||
|
private appReactionsManager: AppReactionsManager,
|
||
|
private type: 'horizontal' | 'vertical',
|
||
|
middleware: ChatReactionsMenu['middleware']
|
||
|
) {
|
||
|
const widthContainer = this.widthContainer = document.createElement('div');
|
||
|
widthContainer.classList.add(REACTIONS_CLASS_NAME + '-container');
|
||
|
widthContainer.classList.add(REACTIONS_CLASS_NAME + '-container-' + type);
|
||
|
|
||
|
const reactionsContainer = this.container = document.createElement('div');
|
||
|
reactionsContainer.classList.add(REACTIONS_CLASS_NAME);
|
||
|
|
||
|
const reactionsScrollable = this.scrollable = type === 'vertical' ? new Scrollable(undefined) : new ScrollableX(undefined);
|
||
|
reactionsContainer.append(reactionsScrollable.container);
|
||
|
reactionsScrollable.onAdditionalScroll = this.onScroll;
|
||
|
reactionsScrollable.setListeners();
|
||
|
|
||
|
reactionsScrollable.container.classList.add('no-scrollbar');
|
||
|
|
||
|
['big'].forEach(type => {
|
||
|
const bubble = document.createElement('div');
|
||
|
bubble.classList.add(REACTIONS_CLASS_NAME + '-bubble', REACTIONS_CLASS_NAME + '-bubble-' + type);
|
||
|
reactionsContainer.append(bubble);
|
||
|
});
|
||
|
|
||
|
this.reactionsMap = new Map();
|
||
|
this.animationGroup = 'CHAT-MENU-REACTIONS-' + Date.now();
|
||
|
animationIntersector.setOverrideIdleGroup(this.animationGroup, true);
|
||
|
|
||
|
if(!IS_TOUCH_SUPPORTED) {
|
||
|
reactionsContainer.addEventListener('mousemove', this.onMouseMove);
|
||
|
}
|
||
|
|
||
|
attachClickEvent(reactionsContainer, (e) => {
|
||
|
const reactionDiv = findUpClassName(e.target, REACTION_CLASS_NAME);
|
||
|
if(!reactionDiv) return;
|
||
|
|
||
|
const players = this.reactionsMap.get(reactionDiv);
|
||
|
if(!players) return;
|
||
|
|
||
|
this.appReactionsManager.sendReaction(this.message, players.reaction);
|
||
|
});
|
||
|
|
||
|
widthContainer.append(reactionsContainer);
|
||
|
|
||
|
this.middleware = middleware ?? getMiddleware();
|
||
|
}
|
||
|
|
||
|
public init(message: Message.message) {
|
||
|
this.message = message;
|
||
|
|
||
|
const middleware = this.middleware.get();
|
||
|
// const result = Promise.resolve(this.appReactionsManager.getAvailableReactionsForPeer(message.peerId)).then((res) => pause(1000).then(() => res));
|
||
|
const result = this.appReactionsManager.getAvailableReactionsByMessage(message);
|
||
|
callbackify(result, (reactions) => {
|
||
|
if(!middleware() || !reactions.length) return;
|
||
|
reactions.forEach(reaction => {
|
||
|
this.renderReaction(reaction);
|
||
|
});
|
||
|
|
||
|
const setVisible = () => {
|
||
|
this.container.classList.add('is-visible');
|
||
|
};
|
||
|
|
||
|
if(result instanceof Promise) {
|
||
|
fastRaf(setVisible);
|
||
|
} else {
|
||
|
setVisible();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public cleanup() {
|
||
|
this.middleware.clean();
|
||
|
this.scrollable.removeListeners();
|
||
|
this.reactionsMap.clear();
|
||
|
animationIntersector.setOverrideIdleGroup(this.animationGroup, false);
|
||
|
animationIntersector.checkAnimations(true, this.animationGroup, true);
|
||
|
}
|
||
|
|
||
|
private onScroll = () => {
|
||
|
this.reactionsMap.forEach((players, div) => {
|
||
|
this.onScrollProcessItem(div, players);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
private renderReaction(reaction: AvailableReaction) {
|
||
|
const reactionDiv = document.createElement('div');
|
||
|
reactionDiv.classList.add(REACTION_CLASS_NAME);
|
||
|
|
||
|
const scaleContainer = document.createElement('div');
|
||
|
scaleContainer.classList.add(REACTION_CLASS_NAME + '-scale');
|
||
|
|
||
|
const appearWrapper = document.createElement('div');
|
||
|
let selectWrapper: HTMLElement;;
|
||
|
appearWrapper.classList.add(REACTION_CLASS_NAME + '-appear');
|
||
|
|
||
|
if(rootScope.settings.animationsEnabled) {
|
||
|
selectWrapper = document.createElement('div');
|
||
|
selectWrapper.classList.add(REACTION_CLASS_NAME + '-select', 'hide');
|
||
|
}
|
||
|
|
||
|
const players: ChatReactionsMenuPlayers = {
|
||
|
selectWrapper,
|
||
|
appearWrapper,
|
||
|
reaction: reaction.reaction
|
||
|
};
|
||
|
this.reactionsMap.set(reactionDiv, players);
|
||
|
|
||
|
const middleware = this.middleware.get();
|
||
|
|
||
|
const hoverScale = IS_TOUCH_SUPPORTED ? 1 : 1.25;
|
||
|
const size = REACTION_SIZE * hoverScale;
|
||
|
|
||
|
const options = {
|
||
|
width: size,
|
||
|
height: size,
|
||
|
skipRatio: 1,
|
||
|
needFadeIn: false,
|
||
|
withThumb: false,
|
||
|
group: this.animationGroup,
|
||
|
middleware
|
||
|
};
|
||
|
|
||
|
if(!rootScope.settings.animationsEnabled) {
|
||
|
delete options.needFadeIn;
|
||
|
delete options.withThumb;
|
||
|
|
||
|
wrapSticker({
|
||
|
doc: reaction.static_icon,
|
||
|
div: appearWrapper,
|
||
|
...options
|
||
|
});
|
||
|
} else {
|
||
|
let isFirst = true;
|
||
|
wrapSticker({
|
||
|
doc: reaction.appear_animation,
|
||
|
div: appearWrapper,
|
||
|
play: true,
|
||
|
...options
|
||
|
}).then(player => {
|
||
|
assumeType<RLottiePlayer>(player);
|
||
|
|
||
|
players.appear = player;
|
||
|
|
||
|
player.addEventListener('enterFrame', (frameNo) => {
|
||
|
if(player.maxFrame === frameNo) {
|
||
|
selectLoadPromise.then((selectPlayer) => {
|
||
|
assumeType<RLottiePlayer>(selectPlayer);
|
||
|
appearWrapper.classList.add('hide');
|
||
|
selectWrapper.classList.remove('hide');
|
||
|
|
||
|
if(isFirst) {
|
||
|
players.select = selectPlayer;
|
||
|
isFirst = false;
|
||
|
}
|
||
|
}, noop);
|
||
|
}
|
||
|
});
|
||
|
}, noop);
|
||
|
|
||
|
const selectLoadPromise = wrapSticker({
|
||
|
doc: reaction.select_animation,
|
||
|
div: selectWrapper,
|
||
|
...options
|
||
|
}).then(player => {
|
||
|
assumeType<RLottiePlayer>(player);
|
||
|
|
||
|
return lottieLoader.waitForFirstFrame(player);
|
||
|
}).catch(noop);
|
||
|
}
|
||
|
|
||
|
scaleContainer.append(appearWrapper);
|
||
|
selectWrapper && scaleContainer.append(selectWrapper);
|
||
|
reactionDiv.append(scaleContainer);
|
||
|
this.scrollable.append(reactionDiv);
|
||
|
}
|
||
|
|
||
|
private onScrollProcessItem(div: HTMLElement, players: ChatReactionsMenuPlayers) {
|
||
|
// return;
|
||
|
|
||
|
const scaleContainer = div.firstElementChild as HTMLElement;
|
||
|
const visibleRect = getVisibleRect(div, this.scrollable.container);
|
||
|
let transform: string;
|
||
|
if(!visibleRect) {
|
||
|
if(!players.appearWrapper.classList.contains('hide') || !players.appear) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(players.select) {
|
||
|
players.select.stop();
|
||
|
}
|
||
|
|
||
|
players.appear.stop();
|
||
|
players.appear.autoplay = true;
|
||
|
players.appearWrapper.classList.remove('hide');
|
||
|
players.selectWrapper.classList.add('hide');
|
||
|
|
||
|
transform = '';
|
||
|
} else if(visibleRect.overflow.left || visibleRect.overflow.right) {
|
||
|
const diff = Math.abs(visibleRect.rect.left - visibleRect.rect.right);
|
||
|
const scale = Math.min(diff ** 2 / REACTION_CONTAINER_SIZE ** 2, 1);
|
||
|
|
||
|
transform = 'scale(' + scale + ')';
|
||
|
} else {
|
||
|
transform = '';
|
||
|
}
|
||
|
|
||
|
if(CAN_USE_TRANSFORM) {
|
||
|
scaleContainer.style.transform = transform;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private onMouseMove = (e: MouseEvent) => {
|
||
|
const reactionDiv = findUpClassName(e.target, REACTION_CLASS_NAME);
|
||
|
if(!reactionDiv) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const players = this.reactionsMap.get(reactionDiv);
|
||
|
if(!players) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// do not play select animation when appearing
|
||
|
if(!players.appear?.paused) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const player = players.select;
|
||
|
if(!player) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(player.paused) {
|
||
|
player.autoplay = true;
|
||
|
player.restart();
|
||
|
}
|
||
|
};
|
||
|
}
|