Browse Source

Reactions menu

master
Eduard Kuzmenko 2 years ago
parent
commit
980755bd38
  1. 38
      src/components/animationIntersector.ts
  2. 4
      src/components/chat/bubbles.ts
  3. 7
      src/components/chat/chat.ts
  4. 464
      src/components/chat/contextMenu.ts
  5. 109
      src/components/scrollable.ts
  6. 27
      src/components/sidebarLeft/tabs/generalSettings.ts
  7. 26
      src/components/wrappers.ts
  8. 6
      src/helpers/blob.ts
  9. 10
      src/helpers/dom/getVisibleRect.ts
  10. 2
      src/lang.ts
  11. 18
      src/layer.d.ts
  12. 4
      src/lib/appManagers/appImManager.ts
  13. 77
      src/lib/appManagers/appReactionsManager.ts
  14. 6
      src/lib/mtproto/referenceDatabase.ts
  15. 6
      src/lib/rlottie/lottieLoader.ts
  16. 1
      src/lib/rlottie/rlottiePlayer.ts
  17. 20
      src/scripts/generate_mtproto_types.js
  18. 11
      src/scripts/in/schema_additional_params.json
  19. 79
      src/scss/partials/_button.scss
  20. 6
      src/scss/partials/_row.scss
  21. 2
      tweb-design

38
src/components/animationIntersector.ts

@ -8,7 +8,7 @@ import rootScope from "../lib/rootScope"; @@ -8,7 +8,7 @@ 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 { indexOfAndSplice } from "../helpers/array";
import { forEachReverse, indexOfAndSplice } from "../helpers/array";
import RLottiePlayer from "../lib/rlottie/rlottiePlayer";
export interface AnimationItem {
@ -18,9 +18,10 @@ export interface AnimationItem { @@ -18,9 +18,10 @@ export interface AnimationItem {
};
export class AnimationIntersector {
public observer: IntersectionObserver;
private observer: IntersectionObserver;
private visible: Set<AnimationItem> = new Set();
private overrideIdleGroups: Set<string>;
private byGroups: {[group: string]: AnimationItem[]} = {};
private lockedGroups: {[group: string]: true} = {};
private onlyOnePlayableGroup: string = '';
@ -30,7 +31,7 @@ export class AnimationIntersector { @@ -30,7 +31,7 @@ export class AnimationIntersector {
constructor() {
this.observer = new IntersectionObserver((entries) => {
if(rootScope.idle.isIDLE) return;
// if(rootScope.idle.isIDLE) return;
for(const entry of entries) {
const target = entry.target;
@ -61,6 +62,8 @@ export class AnimationIntersector { @@ -61,6 +62,8 @@ export class AnimationIntersector {
}
});
this.overrideIdleGroups = new Set();
rootScope.addEventListener('media_play', ({doc}) => {
if(doc.type === 'round') {
this.videosLocked = true;
@ -76,6 +79,11 @@ export class AnimationIntersector { @@ -76,6 +79,11 @@ export class AnimationIntersector {
});
}
public setOverrideIdleGroup(group: string, override: boolean) {
if(override) this.overrideIdleGroups.add(group);
else this.overrideIdleGroups.delete(group);
}
public getAnimations(element: HTMLElement) {
const found: AnimationItem[] = [];
for(const group in this.byGroups) {
@ -101,8 +109,12 @@ export class AnimationIntersector { @@ -101,8 +109,12 @@ export class AnimationIntersector {
}, 1e3);
}
for(const group in this.byGroups) {
indexOfAndSplice(this.byGroups[group], player);
const group = this.byGroups[player.group];
if(group) {
indexOfAndSplice(group, player);
if(!group.length) {
delete this.byGroups[player.group];
}
}
this.observer.unobserve(el);
@ -127,20 +139,19 @@ export class AnimationIntersector { @@ -127,20 +139,19 @@ export class AnimationIntersector {
}
public checkAnimations(blurred?: boolean, group?: string, destroy = false) {
if(rootScope.idle.isIDLE) return;
const groups = group /* && false */ ? [group] : Object.keys(this.byGroups);
// if(rootScope.idle.isIDLE) return;
if(group && !this.byGroups[group]) {
if(group !== undefined && !this.byGroups[group]) {
//console.warn('no animation group:', group);
this.byGroups[group] = [];
return;
}
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups);
for(const group of groups) {
const animations = this.byGroups[group];
animations.forEach(player => {
forEachReverse(animations, (player) => {
this.checkAnimation(player, blurred, destroy);
});
}
@ -162,7 +173,8 @@ export class AnimationIntersector { @@ -162,7 +173,8 @@ export class AnimationIntersector {
} else if(animation.paused &&
this.visible.has(player) &&
animation.autoplay &&
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group)
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group) &&
(!rootScope.idle.isIDLE || this.overrideIdleGroups.has(player.group))
) {
//console.warn('play animation:', animation);
animation.play();
@ -211,4 +223,4 @@ const animationIntersector = new AnimationIntersector(); @@ -211,4 +223,4 @@ const animationIntersector = new AnimationIntersector();
if(MOUNT_CLASS_TO) {
MOUNT_CLASS_TO.animationIntersector = animationIntersector;
}
export default animationIntersector;
export default animationIntersector;

4
src/components/chat/bubbles.ts

@ -2196,7 +2196,7 @@ export default class ChatBubbles { @@ -2196,7 +2196,7 @@ export default class ChatBubbles {
// this.ladderDeferred.resolve();
scrollable.lastScrollDirection = 0;
scrollable.lastScrollTop = 0;
scrollable.lastScrollPosition = 0;
replaceContent(scrollable.container, chatInner);
animationIntersector.unlockGroup(CHAT_ANIMATION_GROUP);
@ -3633,7 +3633,7 @@ export default class ChatBubbles { @@ -3633,7 +3633,7 @@ export default class ChatBubbles {
//this.scrollable.scrollTop = this.scrollable.scrollHeight;
//isTouchSupported && isApple && (this.scrollable.container.style.overflow = '');
this.scrollable.lastScrollTop = newScrollTop;
this.scrollable.lastScrollPosition = newScrollTop;
this.scrollable.lastScrollDirection = 0;
if(IS_SAFARI/* && !isAppleMobile */) { // * fix blinking and jumping

7
src/components/chat/chat.ts

@ -23,6 +23,7 @@ import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager"; @@ -23,6 +23,7 @@ import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager";
import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager";
import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager";
import type { AppGroupCallsManager } from "../../lib/appManagers/appGroupCallsManager";
import type { AppReactionsManager } from "../../lib/appManagers/appReactionsManager";
import type { State } from "../../lib/appManagers/appStateManager";
import type stateStorage from '../../lib/stateStorage';
import EventListenerBase from "../../helpers/eventListenerBase";
@ -92,7 +93,8 @@ export default class Chat extends EventListenerBase<{ @@ -92,7 +93,8 @@ export default class Chat extends EventListenerBase<{
public appNotificationsManager: AppNotificationsManager,
public appEmojiManager: AppEmojiManager,
public appMessagesIdsManager: AppMessagesIdsManager,
public appGroupCallsManager: AppGroupCallsManager
public appGroupCallsManager: AppGroupCallsManager,
public appReactionsManager: AppReactionsManager
) {
super();
@ -185,7 +187,7 @@ export default class Chat extends EventListenerBase<{ @@ -185,7 +187,7 @@ export default class Chat extends EventListenerBase<{
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager, this.appChatsManager);
this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager);
this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager);
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager, this.appReactionsManager);
if(this.type === 'chat') {
this.topbar.constructUtils();
@ -240,6 +242,7 @@ export default class Chat extends EventListenerBase<{ @@ -240,6 +242,7 @@ export default class Chat extends EventListenerBase<{
this.topbar.destroy();
this.bubbles.destroy();
this.input.destroy();
this.contextMenu && this.contextMenu.destroy();
delete this.topbar;
delete this.bubbles;

464
src/components/chat/contextMenu.ts

@ -9,6 +9,7 @@ import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; @@ -9,6 +9,7 @@ import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type { AppPollsManager } from "../../lib/appManagers/appPollsManager";
import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager";
import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager";
import type { AppReactionsManager } from "../../lib/appManagers/appReactionsManager";
import type Chat from "./chat";
import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport";
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
@ -24,10 +25,211 @@ import findUpClassName from "../../helpers/dom/findUpClassName"; @@ -24,10 +25,211 @@ import findUpClassName from "../../helpers/dom/findUpClassName";
import { cancelEvent } from "../../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty";
import { Message, Poll, Chat as MTChat, MessageMedia } from "../../layer";
import { Message, Poll, Chat as MTChat, MessageMedia, AvailableReaction } from "../../layer";
import PopupReportMessages from "../popups/reportMessages";
import assumeType from "../../helpers/assumeType";
import PopupSponsored from "../popups/sponsored";
import { ScrollableX } from "../scrollable";
import { wrapSticker } from "../wrappers";
import RLottiePlayer from "../../lib/rlottie/rlottiePlayer";
import getVisibleRect from "../../helpers/dom/getVisibleRect";
import ListenerSetter from "../../helpers/listenerSetter";
import animationIntersector from "../animationIntersector";
import { getMiddleware } from "../../helpers/middleware";
import noop from "../../helpers/noop";
const REACTIONS_CLASS_NAME = 'btn-menu-reactions';
const REACTION_CLASS_NAME = REACTIONS_CLASS_NAME + '-reaction';
const REACTION_SIZE = 24;
const PADDING = 4;
const REACTION_CONTAINER_SIZE = REACTION_SIZE + PADDING * 2;
type ChatReactionsMenuPlayers = {
select?: RLottiePlayer,
appear?: RLottiePlayer,
selectWrapper: HTMLElement,
appearWrapper: HTMLElement
};
export class ChatReactionsMenu {
public container: HTMLElement;
private reactionsMap: Map<HTMLElement, ChatReactionsMenuPlayers>;
private scrollable: ScrollableX;
private animationGroup: string;
private middleware: ReturnType<typeof getMiddleware>;
constructor(
private appReactionsManager: AppReactionsManager
) {
const reactionsContainer = this.container = document.createElement('div');
reactionsContainer.classList.add(REACTIONS_CLASS_NAME);
const reactionsScrollable = this.scrollable = new ScrollableX(undefined);
reactionsContainer.append(reactionsScrollable.container);
reactionsScrollable.onAdditionalScroll = this.onScroll;
reactionsScrollable.setListeners();
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);
}
this.middleware = getMiddleware();
const middleware = this.middleware.get();
appReactionsManager.getAvailableReactions().then(reactions => {
if(!middleware()) return;
reactions.forEach(reaction => {
this.renderReaction(reaction);
});
});
}
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');
const selectWrapper = document.createElement('div');
appearWrapper.classList.add(REACTION_CLASS_NAME + '-appear');
selectWrapper.classList.add(REACTION_CLASS_NAME + '-select', 'hide');
const hoverScale = IS_TOUCH_SUPPORTED ? 1 : 1.25;
const size = REACTION_SIZE * hoverScale;
const players: ChatReactionsMenuPlayers = {
selectWrapper,
appearWrapper
};
this.reactionsMap.set(reactionDiv, players);
const middleware = this.middleware.get();
const options = {
width: size,
height: size,
skipRatio: 1,
needFadeIn: false,
withThumb: false,
group: this.animationGroup,
middleware
};
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
}).catch(noop);
scaleContainer.append(appearWrapper, selectWrapper);
reactionDiv.append(scaleContainer);
this.scrollable.append(reactionDiv);
}
private onScrollProcessItem(div: HTMLElement, players: ChatReactionsMenuPlayers) {
if(!players.appear) {
return;
}
const scaleContainer = div.firstElementChild as HTMLElement;
const visibleRect = getVisibleRect(div, this.scrollable.container);
if(!visibleRect) {
if(!players.appearWrapper.classList.contains('hide')) {
return;
}
if(players.select) {
players.select.stop();
}
players.appear.stop();
players.appear.autoplay = true;
players.appearWrapper.classList.remove('hide');
players.selectWrapper.classList.add('hide');
scaleContainer.style.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);
scaleContainer.style.transform = `scale(${scale})`;
} else {
scaleContainer.style.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();
}
};
}
export default class ChatContextMenu {
private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean, withSelection?: true, isSponsored?: true})[];
@ -40,116 +242,27 @@ export default class ChatContextMenu { @@ -40,116 +242,27 @@ export default class ChatContextMenu {
private isTextSelected: boolean;
private isAnchorTarget: boolean;
private isUsernameTarget: boolean;
private isSponsored: boolean;
private isOverBubble: boolean;
private peerId: PeerId;
private mid: number;
private message: Message.message | Message.messageService;
private noForwards: boolean;
constructor(private attachTo: HTMLElement,
private reactionsMenu: ChatReactionsMenu;
private listenerSetter: ListenerSetter;
constructor(
private attachTo: HTMLElement,
private chat: Chat,
private appMessagesManager: AppMessagesManager,
private appPeersManager: AppPeersManager,
private appPollsManager: AppPollsManager,
private appDocsManager: AppDocsManager,
private appMessagesIdsManager: AppMessagesIdsManager
private appMessagesIdsManager: AppMessagesIdsManager,
private appReactionsManager: AppReactionsManager
) {
const onContextMenu = (e: MouseEvent | Touch | TouchEvent) => {
if(this.init) {
this.init();
this.init = null;
}
let bubble: HTMLElement, contentWrapper: HTMLElement;
try {
contentWrapper = findUpClassName(e.target, 'bubble-content-wrapper');
bubble = contentWrapper ? contentWrapper.parentElement : findUpClassName(e.target, 'bubble');
} catch(e) {}
// ! context menu click by date bubble (there is no pointer-events)
if(!bubble || bubble.classList.contains('bubble-first')) return;
if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
if(e instanceof MouseEvent || e.hasOwnProperty('cancelBubble')) (e as any).cancelBubble = true;
let mid = +bubble.dataset.mid;
if(!mid) return;
const isSponsored = mid < 0;
this.isSelectable = this.chat.selection.canSelectBubble(bubble);
this.peerId = this.chat.peerId;
//this.msgID = msgID;
this.target = e.target as HTMLElement;
this.isTextSelected = !isSelectionEmpty();
this.isAnchorTarget = this.target.tagName === 'A' && (
(this.target as HTMLAnchorElement).target === '_blank' ||
this.target.classList.contains('anchor-url')
);
this.isUsernameTarget = this.target.tagName === 'A' && this.target.classList.contains('mention');
// * если открыть контекстное меню для альбома не по бабблу, и последний элемент не выбран, чтобы показать остальные пункты
if(chat.selection.isSelecting && !contentWrapper) {
if(isSponsored) {
return;
}
const mids = this.chat.getMidsByMid(mid);
if(mids.length > 1) {
const selectedMid = this.chat.selection.isMidSelected(this.peerId, mid) ?
mid :
mids.find(mid => this.chat.selection.isMidSelected(this.peerId, mid));
if(selectedMid) {
mid = selectedMid;
}
}
}
const groupedItem = findUpClassName(this.target, 'grouped-item');
this.isTargetAGroupedItem = !!groupedItem;
if(groupedItem) {
this.mid = +groupedItem.dataset.mid;
} else {
this.mid = mid;
}
this.isSelected = this.chat.selection.isMidSelected(this.peerId, this.mid);
this.message = this.chat.getMessage(this.mid);
if(isSponsored) {
this.buttons.forEach(button => {
button.element.classList.toggle('hide', !button.isSponsored);
});
} else {
this.noForwards = !this.appMessagesManager.canForward(this.message);
this.buttons.forEach(button => {
let good: boolean;
//if((appImManager.chatSelection.isSelecting && !button.withSelection) || (button.withSelection && !appImManager.chatSelection.isSelecting)) {
if(chat.selection.isSelecting && !button.withSelection) {
good = false;
} else {
good = contentWrapper || IS_TOUCH_SUPPORTED || true ?
button.verify() :
button.notDirect && button.verify() && button.notDirect();
}
button.element.classList.toggle('hide', !good);
});
}
const side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right';
//bubble.parentElement.append(this.element);
//appImManager.log('contextmenu', e, bubble, side);
positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, this.element, side);
openBtnMenu(this.element, () => {
this.mid = 0;
this.peerId = undefined;
this.target = null;
});
};
this.listenerSetter = new ListenerSetter();
if(IS_TOUCH_SUPPORTED/* && false */) {
attachClickEvent(attachTo, (e) => {
@ -167,13 +280,129 @@ export default class ChatContextMenu { @@ -167,13 +280,129 @@ export default class ChatContextMenu {
cancelEvent(e);
//onContextMenu((e as TouchEvent).changedTouches[0]);
// onContextMenu((e as TouchEvent).changedTouches ? (e as TouchEvent).changedTouches[0] : e as MouseEvent);
onContextMenu(e);
this.onContextMenu(e);
}
}, {listenerSetter: this.chat.bubbles.listenerSetter});
} else attachContextMenuListener(attachTo, onContextMenu, this.chat.bubbles.listenerSetter);
} else attachContextMenuListener(attachTo, this.onContextMenu, this.chat.bubbles.listenerSetter);
}
private init() {
private onContextMenu = (e: MouseEvent | Touch | TouchEvent) => {
let bubble: HTMLElement, contentWrapper: HTMLElement;
try {
contentWrapper = findUpClassName(e.target, 'bubble-content-wrapper');
bubble = contentWrapper ? contentWrapper.parentElement : findUpClassName(e.target, 'bubble');
} catch(e) {}
// ! context menu click by date bubble (there is no pointer-events)
if(!bubble || bubble.classList.contains('bubble-first')) return;
let element = this.element;
if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault();
if(element && element.classList.contains('active')) {
return false;
}
if(e instanceof MouseEvent || e.hasOwnProperty('cancelBubble')) (e as any).cancelBubble = true;
let mid = +bubble.dataset.mid;
if(!mid) return;
const isSponsored = this.isSponsored = mid < 0;
this.isSelectable = this.chat.selection.canSelectBubble(bubble);
this.peerId = this.chat.peerId;
//this.msgID = msgID;
this.target = e.target as HTMLElement;
this.isTextSelected = !isSelectionEmpty();
this.isAnchorTarget = this.target.tagName === 'A' && (
(this.target as HTMLAnchorElement).target === '_blank' ||
this.target.classList.contains('anchor-url')
);
this.isUsernameTarget = this.target.tagName === 'A' && this.target.classList.contains('mention');
// * если открыть контекстное меню для альбома не по бабблу, и последний элемент не выбран, чтобы показать остальные пункты
if(this.chat.selection.isSelecting && !contentWrapper) {
if(isSponsored) {
return;
}
const mids = this.chat.getMidsByMid(mid);
if(mids.length > 1) {
const selectedMid = this.chat.selection.isMidSelected(this.peerId, mid) ?
mid :
mids.find(mid => this.chat.selection.isMidSelected(this.peerId, mid));
if(selectedMid) {
mid = selectedMid;
}
}
}
this.isOverBubble = !!contentWrapper;
const groupedItem = findUpClassName(this.target, 'grouped-item');
this.isTargetAGroupedItem = !!groupedItem;
if(groupedItem) {
this.mid = +groupedItem.dataset.mid;
} else {
this.mid = mid;
}
this.isSelected = this.chat.selection.isMidSelected(this.peerId, this.mid);
this.message = this.chat.getMessage(this.mid);
this.noForwards = !isSponsored && !this.appMessagesManager.canForward(this.message);
const initResult = this.init();
element = initResult.element;
const {cleanup, destroy} = initResult;
const side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right';
//bubble.parentElement.append(element);
//appImManager.log('contextmenu', e, bubble, side);
positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, element, side);
openBtnMenu(element, () => {
this.mid = 0;
this.peerId = undefined;
this.target = null;
cleanup();
setTimeout(() => {
destroy();
}, 300);
});
};
public cleanup() {
this.listenerSetter.removeAll();
this.reactionsMenu && this.reactionsMenu.cleanup();
}
public destroy() {
this.cleanup();
}
private filterButtons(buttons: ChatContextMenu['buttons']) {
if(this.isSponsored) {
return buttons.filter(button => {
return button.isSponsored;
});
} else {
return buttons.filter(button => {
let good: boolean;
//if((appImManager.chatSelection.isSelecting && !button.withSelection) || (button.withSelection && !appImManager.chatSelection.isSelecting)) {
if(this.chat.selection.isSelecting && !button.withSelection) {
good = false;
} else {
good = this.isOverBubble || IS_TOUCH_SUPPORTED || true ?
button.verify() :
button.notDirect && button.verify() && button.notDirect();
}
return good;
});
}
}
private setButtons() {
this.buttons = [{
icon: 'send2',
text: 'MessageScheduleSend',
@ -382,11 +611,32 @@ export default class ChatContextMenu { @@ -382,11 +611,32 @@ export default class ChatContextMenu {
verify: () => false,
isSponsored: true
}];
}
this.element = ButtonMenu(this.buttons, this.chat.bubbles.listenerSetter);
this.element.id = 'bubble-contextmenu';
this.element.classList.add('contextmenu');
this.chat.container.append(this.element);
private init() {
this.cleanup();
this.setButtons();
const filteredButtons = this.filterButtons(this.buttons);
const element = this.element = ButtonMenu(filteredButtons, this.listenerSetter);
element.id = 'bubble-contextmenu';
element.classList.add('contextmenu');
const reactionsMenu = this.reactionsMenu = new ChatReactionsMenu(this.appReactionsManager);
element.prepend(reactionsMenu.container);
this.chat.container.append(element);
return {
element,
cleanup: () => {
this.cleanup();
reactionsMenu.cleanup();
},
destroy: () => {
element.remove();
}
};
}
private onSendScheduledClick = () => {

109
src/components/scrollable.ts

@ -55,12 +55,25 @@ const scrollsIntersector = new IntersectionObserver(entries => { @@ -55,12 +55,25 @@ const scrollsIntersector = new IntersectionObserver(entries => {
export class ScrollableBase {
protected log: ReturnType<typeof logger>;
public splitUp: HTMLElement;
public onScrollMeasure: number = 0;
protected onScroll: () => void;
public lastScrollPosition: number = 0;
public lastScrollDirection: number = 0;
public onAdditionalScroll: () => void;
public onScrolledTop: () => void;
public onScrolledBottom: () => void;
public isHeavyAnimationInProgress = false;
protected needCheckAfterAnimation = false;
public checkForTriggers?: () => void;
public scrollProperty: 'scrollTop' | 'scrollLeft';
private removeHeavyAnimationListener: () => void;
constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
this.container.classList.add('scrollable');
@ -74,11 +87,15 @@ export class ScrollableBase { @@ -74,11 +87,15 @@ export class ScrollableBase {
//this.onScroll();
}
protected setListeners() {
public setListeners() {
if(this.removeHeavyAnimationListener) {
return;
}
window.addEventListener('resize', this.onScroll, {passive: true});
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
useHeavyAnimationCheck(() => {
this.removeHeavyAnimationListener = useHeavyAnimationCheck(() => {
this.isHeavyAnimationInProgress = true;
if(this.onScrollMeasure) {
@ -95,6 +112,17 @@ export class ScrollableBase { @@ -95,6 +112,17 @@ export class ScrollableBase {
});
}
public removeListeners() {
if(!this.removeHeavyAnimationListener) {
return;
}
window.removeEventListener('resize', this.onScroll);
this.container.removeEventListener('scroll', this.onScroll, {capture: true});
this.removeHeavyAnimationListener();
}
public append(element: HTMLElement) {
this.container.append(element);
}
@ -106,42 +134,6 @@ export class ScrollableBase { @@ -106,42 +134,6 @@ export class ScrollableBase {
container: this.container
});
}
}
export type SliceSides = 'top' | 'bottom';
export type SliceSidesContainer = {[k in SliceSides]: boolean};
export default class Scrollable extends ScrollableBase {
public splitUp: HTMLElement;
public padding: HTMLElement;
public onAdditionalScroll: () => void;
public onScrolledTop: () => void;
public onScrolledBottom: () => void;
public lastScrollTop: number = 0;
public lastScrollDirection: number = 0;
public loadedAll: SliceSidesContainer = {top: true, bottom: false};
constructor(el: HTMLElement, logPrefix = '', public onScrollOffset = 300, withPaddingContainer?: boolean) {
super(el, logPrefix);
/* if(withPaddingContainer) {
this.padding = document.createElement('div');
this.padding.classList.add('scrollable-padding');
Array.from(this.container.children).forEach(c => this.padding.append(c));
this.container.append(this.padding);
} */
this.container.classList.add('scrollable-y');
this.setListeners();
}
public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el;
this.log('setVirtualContainer:', el, this);
}
public onScroll = () => {
//if(this.debug) {
@ -165,9 +157,9 @@ export default class Scrollable extends ScrollableBase { @@ -165,9 +157,9 @@ export default class Scrollable extends ScrollableBase {
this.onScrollMeasure = window.requestAnimationFrame(() => {
this.onScrollMeasure = 0;
const scrollTop = this.container.scrollTop;
this.lastScrollDirection = this.lastScrollTop === scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollTop = scrollTop;
const scrollPosition = this.container[this.scrollProperty];
this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollPosition = scrollPosition;
if(this.onAdditionalScroll && this.lastScrollDirection !== 0) {
this.onAdditionalScroll();
@ -178,6 +170,35 @@ export default class Scrollable extends ScrollableBase { @@ -178,6 +170,35 @@ export default class Scrollable extends ScrollableBase {
}
});
};
}
export type SliceSides = 'top' | 'bottom';
export type SliceSidesContainer = {[k in SliceSides]: boolean};
export default class Scrollable extends ScrollableBase {
public padding: HTMLElement;
public loadedAll: SliceSidesContainer = {top: true, bottom: false};
constructor(el: HTMLElement, logPrefix = '', public onScrollOffset = 300, withPaddingContainer?: boolean) {
super(el, logPrefix);
/* if(withPaddingContainer) {
this.padding = document.createElement('div');
this.padding.classList.add('scrollable-padding');
Array.from(this.container.children).forEach(c => this.padding.append(c));
this.container.append(this.padding);
} */
this.container.classList.add('scrollable-y');
this.setListeners();
this.scrollProperty = 'scrollTop';
}
public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el;
this.log('setVirtualContainer:', el, this);
}
public checkForTriggers = () => {
if((!this.onScrolledTop && !this.onScrolledBottom)) return;
@ -194,7 +215,7 @@ export default class Scrollable extends ScrollableBase { @@ -194,7 +215,7 @@ export default class Scrollable extends ScrollableBase {
const clientHeight = this.container.clientHeight;
const maxScrollTop = scrollHeight - clientHeight;
const scrollTop = this.lastScrollTop;
const scrollTop = this.lastScrollPosition;
//this.log('checkForTriggers:', scrollTop, maxScrollTop);
@ -253,5 +274,7 @@ export class ScrollableX extends ScrollableBase { @@ -253,5 +274,7 @@ export class ScrollableX extends ScrollableBase {
this.container.addEventListener('wheel', scrollHorizontally, {passive: false});
}
this.scrollProperty = 'scrollLeft';
}
}

27
src/components/sidebarLeft/tabs/generalSettings.ts

@ -20,12 +20,13 @@ import appStickersManager from "../../../lib/appManagers/appStickersManager"; @@ -20,12 +20,13 @@ import appStickersManager from "../../../lib/appManagers/appStickersManager";
import assumeType from "../../../helpers/assumeType";
import { MessagesAllStickers, StickerSet } from "../../../layer";
import RichTextProcessor from "../../../lib/richtextprocessor";
import { wrapStickerSetThumb } from "../../wrappers";
import { wrapSticker, wrapStickerSetThumb } from "../../wrappers";
import LazyLoadQueue from "../../lazyLoadQueue";
import PopupStickers from "../../popups/stickers";
import eachMinute from "../../../helpers/eachMinute";
import { SliderSuperTabEventable } from "../../sliderTab";
import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport";
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
export class RangeSettingSelector {
public container: HTMLDivElement;
@ -285,6 +286,28 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -285,6 +286,28 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
{
const container = section('Telegram.InstalledStickerPacksController');
const reactionsRow = new Row({
titleLangKey: 'Reactions',
havePadding: true,
clickable: () => {
}
});
const quickReactionMediaDiv = document.createElement('div');
quickReactionMediaDiv.classList.add('row-media', 'row-media-small');
appReactionsManager.getQuickReaction().then(reaction => {
wrapSticker({
div: quickReactionMediaDiv,
doc: reaction.static_icon,
width: 32,
height: 32
});
});
reactionsRow.container.append(quickReactionMediaDiv);
const suggestCheckboxField = new CheckboxField({
text: 'Stickers.SuggestStickers',
name: 'suggest',
@ -356,7 +379,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -356,7 +379,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
}
});
container.append(suggestCheckboxField.label, loopCheckboxField.label);
container.append(reactionsRow.container, suggestCheckboxField.label, loopCheckboxField.label);
}
}

26
src/components/wrappers.ts

@ -1118,7 +1118,7 @@ export function renderImageWithFadeIn(container: HTMLElement, @@ -1118,7 +1118,7 @@ export function renderImageWithFadeIn(container: HTMLElement,
// });
// }
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale}: {
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio}: {
doc: MyDocument,
div: HTMLElement,
middleware?: () => boolean,
@ -1133,7 +1133,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1133,7 +1133,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
loop?: boolean,
loadPromises?: Promise<any>[],
needFadeIn?: boolean,
needUpscale?: boolean
needUpscale?: boolean,
skipRatio?: number
}): Promise<RLottiePlayer | void> {
const stickerType = doc.sticker;
@ -1280,7 +1281,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1280,7 +1281,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
.then(async(json) => {
//console.timeEnd('download sticker' + doc.id);
//console.log('loaded sticker:', doc, div/* , blob */);
if(middleware && !middleware()) return;
if(middleware && !middleware()) {
throw new Error('wrapSticker 2 middleware');
}
let animation = await LottieLoader.loadAnimationWorker({
container: div,
@ -1290,14 +1293,17 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1290,14 +1293,17 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
width,
height,
name: 'doc' + doc.id,
needUpscale
}, group, toneIndex);
needUpscale,
skipRatio
}, group, toneIndex, middleware);
//const deferred = deferredPromise<void>();
animation.addEventListener('firstFrame', () => {
const element = div.firstElementChild;
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
if(needFadeIn !== false) {
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && rootScope.settings.animationsEnabled;
}
const cb = () => {
if(element && element !== animation.canvas) {
@ -1325,7 +1331,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1325,7 +1331,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
});
}
appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex);
if(withThumb !== false) {
appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex);
}
//deferred.resolve();
}, {once: true});
@ -1511,7 +1519,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1511,7 +1519,9 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
} else if(stickerType === 1) {
const image = new Image();
const thumbImage = div.firstElementChild !== image && div.firstElementChild;
needFadeIn = (needFadeIn || !downloaded || thumbImage) && rootScope.settings.animationsEnabled;
if(needFadeIn !== false) {
needFadeIn = (needFadeIn || !downloaded || thumbImage) && rootScope.settings.animationsEnabled;
}
image.classList.add('media-sticker');

6
src/helpers/blob.ts

@ -13,9 +13,13 @@ export function readBlobAs(blob: Blob, method: 'readAsText'): Promise<string>; @@ -13,9 +13,13 @@ export function readBlobAs(blob: Blob, method: 'readAsText'): Promise<string>;
export function readBlobAs(blob: Blob, method: 'readAsDataURL'): Promise<string>;
export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer'): Promise<ArrayBuffer>;
export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer' | 'readAsText' | 'readAsDataURL'): Promise<any> {
// const perf = performance.now();
return new Promise<any>((resolve) => {
const reader = new FileReader();
reader.addEventListener('loadend', (e) => resolve(e.target.result));
reader.addEventListener('loadend', (e) => {
// console.log('readBlobAs time:', method, performance.now() - perf);
resolve(e.target.result);
});
reader[method](blob);
});
}

10
src/helpers/dom/getVisibleRect.ts

@ -8,7 +8,7 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT @@ -8,7 +8,7 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT
const rect = element.getBoundingClientRect();
const overflowRect = overflowElement.getBoundingClientRect();
let {top: overflowTop, bottom: overflowBottom} = overflowRect;
let {top: overflowTop, right: overflowRight, bottom: overflowBottom, left: overflowLeft} = overflowRect;
// * respect sticky headers
if(lookForSticky) {
@ -21,8 +21,8 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT @@ -21,8 +21,8 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT
if(rect.top >= overflowBottom
|| rect.bottom <= overflowTop
|| rect.right <= overflowRect.left
|| rect.left >= overflowRect.right) {
|| rect.right <= overflowLeft
|| rect.left >= overflowRight) {
return null;
}
@ -43,9 +43,9 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT @@ -43,9 +43,9 @@ export default function getVisibleRect(element: HTMLElement, overflowElement: HT
return {
rect: {
top: rect.top < overflowTop && overflowTop !== 0 ? (overflow.top = true, ++overflow.vertical, overflowTop) : rect.top,
right: 0,
right: rect.right > overflowRight && overflowRight !== windowWidth ? (overflow.right = true, ++overflow.horizontal, overflowRight) : rect.right,
bottom: rect.bottom > overflowBottom && overflowBottom !== windowHeight ? (overflow.bottom = true, ++overflow.vertical, overflowBottom) : rect.bottom,
left: 0
left: rect.left < overflowLeft && overflowLeft !== 0 ? (overflow.left = true, ++overflow.horizontal, overflowLeft) : rect.left
},
overflow
};

2
src/lang.ts

@ -640,6 +640,8 @@ const lang = { @@ -640,6 +640,8 @@ const lang = {
"RequestToJoinGroupApproved": "Your request to join the group was approved",
"RequestToJoinChannelApproved": "Your request to join the channel was approved",
"Update": "UPDATE",
"Reactions": "Reactions",
"DoubleTapSetting": "Quick Reaction",
// * macos
"AccountSettings.Filters": "Chat Folders",

18
src/layer.d.ts vendored

@ -866,7 +866,6 @@ export namespace Message { @@ -866,7 +866,6 @@ export namespace Message {
replies?: MessageReplies,
edit_date?: number,
post_author?: string,
grouped_id?: string,
reactions?: MessageReactions,
restriction_reason?: Array<RestrictionReason>,
ttl_period?: number,
@ -875,6 +874,7 @@ export namespace Message { @@ -875,6 +874,7 @@ export namespace Message {
peerId?: PeerId,
fromId?: PeerId,
fwdFromId?: PeerId,
grouped_id?: string,
random_id?: string,
rReply?: string,
viaBotId?: PeerId,
@ -3224,10 +3224,10 @@ export namespace Document { @@ -3224,10 +3224,10 @@ export namespace Document {
date: number,
mime_type: string,
size: number,
thumbs?: Array<PhotoSize.photoSize | PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize | PhotoSize.photoPathSize>,
video_thumbs?: Array<VideoSize>,
dc_id: number,
attributes: Array<DocumentAttribute>,
thumbs?: Array<PhotoSize.photoSize | PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize | PhotoSize.photoPathSize>,
type?: 'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo' | 'pdf',
h?: number,
w?: number,
@ -9355,13 +9355,13 @@ export namespace AvailableReaction { @@ -9355,13 +9355,13 @@ export namespace AvailableReaction {
}>,
reaction: string,
title: string,
static_icon: Document,
appear_animation: Document,
select_animation: Document,
activate_animation: Document,
effect_animation: Document,
around_animation?: Document,
center_icon?: Document
static_icon: Document.document,
appear_animation: Document.document,
select_animation: Document.document,
activate_animation: Document.document,
effect_animation: Document.document,
around_animation: Document.document,
center_icon: Document.document
};
}

4
src/lib/appManagers/appImManager.ts

@ -83,6 +83,7 @@ import { Modify, SendMessageEmojiInteractionData } from '../../types'; @@ -83,6 +83,7 @@ import { Modify, SendMessageEmojiInteractionData } from '../../types';
import htmlToSpan from '../../helpers/dom/htmlToSpan';
import getVisibleRect from '../../helpers/dom/getVisibleRect';
import { simulateClickEvent } from '../../helpers/dom/clickEvent';
import appReactionsManager from './appReactionsManager';
//console.log('appImManager included33!');
@ -1427,7 +1428,8 @@ export class AppImManager { @@ -1427,7 +1428,8 @@ export class AppImManager {
appNotificationsManager,
appEmojiManager,
appMessagesIdsManager,
appGroupCallsManager
appGroupCallsManager,
appReactionsManager
);
if(this.chats.length) {

77
src/lib/appManagers/appReactionsManager.ts

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { MOUNT_CLASS_TO } from "../../config/debug";
import assumeType from "../../helpers/assumeType";
import { AvailableReaction, MessagesAvailableReactions } from "../../layer";
import apiManager from "../mtproto/mtprotoworker";
import { ReferenceContext } from "../mtproto/referenceDatabase";
import rootScope from "../rootScope";
import appDocsManager from "./appDocsManager";
const SAVE_DOC_KEYS = [
'static_icon' as const,
'appear_animation' as const,
'select_animation' as const,
'activate_animation' as const,
'effect_animation' as const,
'around_animation' as const,
'center_icon' as const
];
const REFERENCE_CONTEXXT: ReferenceContext = {
type: 'reactions'
};
export class AppReactionsManager {
private availableReactions: AvailableReaction[];
constructor() {
rootScope.addEventListener('language_change', () => {
this.availableReactions = undefined;
this.getAvailableReactions();
});
}
public getAvailableReactions() {
if(this.availableReactions) return Promise.resolve(this.availableReactions);
return apiManager.invokeApiSingleProcess({
method: 'messages.getAvailableReactions',
processResult: (messagesAvailableReactions) => {
assumeType<MessagesAvailableReactions.messagesAvailableReactions>(messagesAvailableReactions);
const availableReactions = this.availableReactions = messagesAvailableReactions.reactions;
for(const reaction of availableReactions) {
for(const key of SAVE_DOC_KEYS) {
if(!reaction[key]) {
continue;
}
reaction[key] = appDocsManager.saveDoc(reaction[key], REFERENCE_CONTEXXT);
}
}
return availableReactions;
},
params: {
hash: 0
}
});
}
public getQuickReaction() {
return Promise.all([
apiManager.getAppConfig(),
this.getAvailableReactions()
]).then(([appConfig, availableReactions]) => {
return availableReactions.find(reaction => reaction.reaction === appConfig.reactions_default);
});
}
}
const appReactionsManager = new AppReactionsManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appReactionsManager = appReactionsManager);
export default appReactionsManager;

6
src/lib/mtproto/referenceDatabase.ts

@ -15,7 +15,7 @@ import apiManager from "./mtprotoworker"; @@ -15,7 +15,7 @@ import apiManager from "./mtprotoworker";
import assumeType from "../../helpers/assumeType";
import { logger } from "../logger";
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds;
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds | ReferenceContext.referenceContextReactions;
export namespace ReferenceContext {
export type referenceContextProfilePhoto = {
type: 'profilePhoto',
@ -31,6 +31,10 @@ export namespace ReferenceContext { @@ -31,6 +31,10 @@ export namespace ReferenceContext {
export type referenceContextEmojiesSounds = {
type: 'emojiesSounds'
};
export type referenceContextReactions = {
type: 'reactions'
};
}
export type ReferenceBytes = Photo.photo['file_reference'];

6
src/lib/rlottie/lottieLoader.ts

@ -130,7 +130,7 @@ export class LottieLoader { @@ -130,7 +130,7 @@ export class LottieLoader {
]).then(() => player);
}
public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', toneIndex = -1): Promise<RLottiePlayer> {
public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', toneIndex = -1, middleware?: () => boolean): Promise<RLottiePlayer> {
if(!this.isWebAssemblySupported) {
return this.loadPromise as any;
}
@ -150,6 +150,10 @@ export class LottieLoader { @@ -150,6 +150,10 @@ export class LottieLoader {
await this.loadLottieWorkers();
}
if(middleware && !middleware()) {
throw new Error('middleware');
}
if(!params.width || !params.height) {
params.width = parseInt(params.container.style.width);
params.height = parseInt(params.container.style.height);

1
src/lib/rlottie/rlottiePlayer.ts

@ -342,6 +342,7 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -342,6 +342,7 @@ export default class RLottiePlayer extends EventListenerBase<{
this.pause();
this.sendQuery('destroy');
if(this.cacheName) cache.releaseCache(this.cacheName);
this.cleanup();
//this.removed = true;
}

20
src/scripts/generate_mtproto_types.js

@ -12,23 +12,37 @@ const replace = require(__dirname + '/in/schema_replace_types.json'); @@ -12,23 +12,37 @@ const replace = require(__dirname + '/in/schema_replace_types.json');
const mtproto = schema.API;
for(const constructor of additional) {
constructor.params.forEach(param => {
const additionalParams = constructor.params || (constructor.params = []);
additionalParams.forEach(param => {
param.type = 'flags.-1?' + param.type;
});
if(constructor.properties) {
additionalParams.push(...constructor.properties);
}
if(constructor.type) {
mtproto.constructors.push(constructor);
}
const realConstructor = constructor.type ? constructor : mtproto.constructors.find(c => c.predicate == constructor.predicate);
if(!constructor.type) {
for(let i = realConstructor.params.length - 1; i >= 0; --i) {
const param = realConstructor.params[i];
if(additionalParams.find(newParam => newParam.name === param.name)) {
realConstructor.params.splice(i, 1);
}
}
}
/* constructor.params.forEach(param => {
const index = realConstructor.params.findIndex(_param => _param.predicate == param.predicate);
if(index !== -1) {
realConstructor.params.splice(index, 1);
}
}); */
realConstructor.params.splice(realConstructor.params.length, 0, ...constructor.params);
realConstructor.params.splice(realConstructor.params.length, 0, ...additionalParams);
}
['Vector t', 'Bool', 'True', 'Null'].forEach(key => {
@ -116,7 +130,7 @@ const processParamType = (type, parseBooleanFlags, overrideTypes) => { @@ -116,7 +130,7 @@ const processParamType = (type, parseBooleanFlags, overrideTypes) => {
default:
//console.log('no such type', type);
//throw new Error('no such type: ' + type);
return isAdditional ? type : camelizeName(type, true);
return isAdditional || type[0] === type[0].toUpperCase() ? type : camelizeName(type, true);
}
};

11
src/scripts/in/schema_additional_params.json

@ -346,4 +346,15 @@ @@ -346,4 +346,15 @@
{"name": "action", "type": "MessageAction.messageActionPhoneCall"}
],
"type": "MessageMedia"
}, {
"predicate": "availableReaction",
"properties": [
{"name": "static_icon", "type": "Document.document"},
{"name": "appear_animation", "type": "Document.document"},
{"name": "select_animation", "type": "Document.document"},
{"name": "activate_animation", "type": "Document.document"},
{"name": "effect_animation", "type": "Document.document"},
{"name": "around_animation", "type": "Document.document"},
{"name": "center_icon", "type": "Document.document"}
]
}]

79
src/scss/partials/_button.scss

@ -96,7 +96,6 @@ @@ -96,7 +96,6 @@
visibility: hidden;
position: absolute;
background-color: var(--surface-color);
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, .24);
z-index: 3;
top: 100%;
padding: .5rem 0;
@ -106,6 +105,11 @@ @@ -106,6 +105,11 @@
transition: opacity var(--btn-menu-transition), transform var(--btn-menu-transition), visibility var(--btn-menu-transition);
font-size: 16px;
&,
&-reactions {
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, .24);
}
body.animation-level-0 & {
transition: none;
}
@ -261,6 +265,79 @@ @@ -261,6 +265,79 @@
padding: 0;
margin: .5rem 0;
}
&-reactions {
--height: 2.5rem;
height: var(--height);
border-radius: 1.25rem;
background-color: var(--surface-color);
position: absolute;
top: calc((var(--height) + .5rem) * -1);
max-width: 100%;
&:after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: " ";
pointer-events: none;
border-radius: inherit;
background: linear-gradient(90deg, var(--surface-color) 0%, transparent 1rem, transparent calc(100% - 1rem), var(--surface-color) 100%);
}
.scrollable-x {
position: relative;
display: flex;
align-items: center;
padding: 0 .25rem;
border-radius: inherit;
}
&-reaction {
width: 2rem;
height: 1.5rem;
flex: 0 0 auto;
padding: 0 .25rem;
cursor: pointer;
&-scale {
width: 100%;
height: 100%;
transform: scale(1);
@include animation-level(2) {
transition: transform .1s linear;
}
}
&-select {
html.no-touch & {
transform: scale(1);
}
html.no-touch body.animation-level-2 & {
transition: transform var(--transition-standard-in);
}
@include hover() {
transform: scale(1.25);
}
}
.media-sticker-wrapper {
position: relative;
width: 100%;
height: 100%;
/* position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0; */
}
}
}
}
.btn-primary {

6
src/scss/partials/_row.scss

@ -114,6 +114,12 @@ @@ -114,6 +114,12 @@
position: absolute !important;
margin: 0 !important;
left: .5rem;
&-small {
width: 32px !important;
height: 32px !important;
left: .75rem !important;
}
}
&.menu-open {

2
tweb-design

@ -1 +1 @@ @@ -1 +1 @@
Subproject commit d10c87ef1aec54cdd2e506fcd980ea848e60eedc
Subproject commit 2c4d08587b77a388d4beb8bc018dcb56ebd8a589
Loading…
Cancel
Save