Browse Source

Stickers: premium effect & toast

master
Eduard Kuzmenko 2 years ago
parent
commit
0c4a99f67d
  1. 3
      src/components/appNavigationController.ts
  2. 2
      src/components/buttonMenu.ts
  3. 2
      src/components/buttonMenuToggle.ts
  4. 3
      src/components/chat/bubbles.ts
  5. 4
      src/components/chat/chat.ts
  6. 4
      src/components/chat/contextMenu.ts
  7. 4
      src/components/groupCall/participants.ts
  8. 2
      src/components/sidebarLeft/index.ts
  9. 52
      src/components/toast.ts
  10. 89
      src/components/wrappers/sticker.ts
  11. 18
      src/components/wrappers/stickerAnimation.ts
  12. 117
      src/helpers/contextMenuController.ts
  13. 4
      src/helpers/dom/clickEvent.ts
  14. 103
      src/helpers/overlayClickHandler.ts
  15. 1
      src/lang.ts
  16. 11
      src/lib/appManagers/appStickersManager.ts
  17. 6
      src/lib/appManagers/utils/docs/getDocumentDownloadOptions.ts
  18. 2
      src/lib/appManagers/utils/download/getDownloadMediaDetails.ts
  19. 5
      src/lib/appManagers/utils/stickers/getStickerEffectThumb.ts
  20. 4
      src/lib/mtproto/apiFileManager.ts
  21. 4
      src/scss/components/_global.scss
  22. 6
      src/scss/partials/_emojiAnimation.scss
  23. 18
      src/scss/style.scss

3
src/components/appNavigationController.ts

@ -15,7 +15,8 @@ import indexOfAndSplice from '../helpers/array/indexOfAndSplice'; @@ -15,7 +15,8 @@ import indexOfAndSplice from '../helpers/array/indexOfAndSplice';
export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' |
'esg' | 'multiselect' | 'input-helper' | 'autocomplete-helper' | 'markup' |
'global-search' | 'voice' | 'mobile-search' | 'filters' | 'global-search-focus',
'global-search' | 'voice' | 'mobile-search' | 'filters' | 'global-search-focus' |
'toast',
onPop: (canAnimate: boolean) => boolean | void,
onEscape?: () => boolean,
noHistory?: boolean,

2
src/components/buttonMenu.ts

@ -62,7 +62,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => { @@ -62,7 +62,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
}
if(!keepOpen) {
contextMenuController.closeBtnMenu();
contextMenuController.close();
}
if(checkboxField && !noCheckboxClickListener/* && result !== false */) {

2
src/components/buttonMenuToggle.ts

@ -49,7 +49,7 @@ const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: (e: Event) => void | @@ -49,7 +49,7 @@ const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: (e: Event) => void |
cancelEvent(e);
if(el.classList.contains('menu-open')) {
contextMenuController.closeBtnMenu();
contextMenuController.close();
} else {
const result = onOpen && onOpen(e);
const open = () => {

3
src/components/chat/bubbles.ts

@ -4016,7 +4016,8 @@ export default class ChatBubbles { @@ -4016,7 +4016,8 @@ export default class ChatBubbles {
loop: true,
emoji: bubble.classList.contains('emoji-big') ? messageMessage : undefined,
withThumb: true,
loadPromises
loadPromises,
isOut
});
} else if(doc.type === 'video' || doc.type === 'gif' || doc.type === 'round'/* && doc.size <= 20e6 */) {
// this.log('never get free 2', doc);

4
src/components/chat/chat.ts

@ -605,13 +605,13 @@ export default class Chat extends EventListenerBase<{ @@ -605,13 +605,13 @@ export default class Chat extends EventListenerBase<{
}
public isOurMessage(message: Message.message | Message.messageService) {
return message.fromId === rootScope.myId || (message.pFlags.out && this.isMegagroup);
return message.fromId === rootScope.myId || (!!message.pFlags.out && this.isMegagroup);
}
public isOutMessage(message: Message.message | Message.messageService) {
const fwdFrom = (message as Message.message).fwd_from;
const isOut = this.isOurMessage(message) && (!fwdFrom || this.peerId !== rootScope.myId);
return isOut;
return !!isOut;
}
public isAvatarNeeded(message: Message.message | Message.messageService) {

4
src/components/chat/contextMenu.ts

@ -411,8 +411,8 @@ export default class ChatContextMenu { @@ -411,8 +411,8 @@ export default class ChatContextMenu {
if(!doc) return false;
let hasTarget = !!IS_TOUCH_SUPPORTED;
const isGoodType = !doc.type || !(['gif', 'video', 'sticker'] as MyDocument['type'][]).includes(doc.type);
if(isGoodType) hasTarget = hasTarget || !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio');
const isGoodType = !doc.type || !(['gif', 'video'/* , 'sticker' */] as MyDocument['type'][]).includes(doc.type);
if(isGoodType) hasTarget ||= !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio') || !!findUpClassName(this.target, 'media-sticker-wrapper');
return isGoodType && hasTarget;
}
}, {

4
src/components/groupCall/participants.ts

@ -136,7 +136,7 @@ export class GroupCallParticipantContextMenu { @@ -136,7 +136,7 @@ export class GroupCallParticipantContextMenu {
if(this.instance.id === groupCallId) {
const peerId = getPeerId(participant.peer);
if(this.targetPeerId === peerId) {
contextMenuController.closeBtnMenu();
contextMenuController.close();
}
}
});
@ -147,7 +147,7 @@ export class GroupCallParticipantContextMenu { @@ -147,7 +147,7 @@ export class GroupCallParticipantContextMenu {
appendTo = isFull ? PopupElement.getPopups(PopupGroupCall)[0].getContainer(): document.body;
if(!isFull) {
contextMenuController.closeBtnMenu();
contextMenuController.close();
}
}, listenerSetter);
}

2
src/components/sidebarLeft/index.ts

@ -240,7 +240,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -240,7 +240,7 @@ export class AppSidebarLeft extends SidebarSlider {
btnMenuFooter.classList.add('btn-menu-footer');
btnMenuFooter.addEventListener(CLICK_EVENT_NAME, (e) => {
e.stopPropagation();
contextMenuController.closeBtnMenu();
contextMenuController.close();
});
const t = document.createElement('span');
t.classList.add('btn-menu-footer-text');

52
src/components/toast.ts

@ -5,24 +5,58 @@ @@ -5,24 +5,58 @@
*/
import replaceContent from '../helpers/dom/replaceContent';
import OverlayClickHandler from '../helpers/overlayClickHandler';
import {FormatterArguments, i18n, LangPackKey} from '../lib/langPack';
const toastEl = document.createElement('div');
toastEl.classList.add('toast');
export function toast(content: string | Node) {
replaceContent(toastEl, content);
document.body.append(toastEl);
let timeout: number;
const x = new OverlayClickHandler('toast');
x.addEventListener('toggle', (open) => {
if(!open) {
hideToast();
}
});
export function hideToast() {
x.close();
toastEl.classList.remove('is-visible');
timeout && clearTimeout(+timeout);
if(toastEl.dataset.timeout) clearTimeout(+toastEl.dataset.timeout);
toastEl.dataset.timeout = '' + setTimeout(() => {
timeout = window.setTimeout(() => {
toastEl.remove();
delete toastEl.dataset.timeout;
}, 3000);
timeout = undefined;
}, 200);
}
export function toast(content: string | Node, onClose?: () => void) {
x.close();
replaceContent(toastEl, content);
if(!toastEl.parentElement) {
document.body.append(toastEl);
void toastEl.offsetLeft; // reflow
}
toastEl.classList.add('is-visible');
timeout && clearTimeout(+timeout);
x.open(toastEl);
timeout = window.setTimeout(hideToast, 3000);
if(onClose) {
x.addEventListener('toggle', onClose, {once: true});
}
}
export function toastNew(options: Partial<{
langPackKey: LangPackKey,
langPackArguments: FormatterArguments
langPackArguments: FormatterArguments,
onClose: () => void
}>) {
toast(i18n(options.langPackKey, options.langPackArguments));
toast(i18n(options.langPackKey, options.langPackArguments), options.onClose);
}

89
src/components/wrappers/sticker.ts

@ -19,13 +19,14 @@ import onMediaLoad from '../../helpers/onMediaLoad'; @@ -19,13 +19,14 @@ import onMediaLoad from '../../helpers/onMediaLoad';
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
import throttle from '../../helpers/schedulers/throttle';
import sequentialDom from '../../helpers/sequentialDom';
import {PhotoSize} from '../../layer';
import {PhotoSize, VideoSize} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager';
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import appImManager from '../../lib/appManagers/appImManager';
import {AppManagers} from '../../lib/appManagers/managers';
import getServerMessageId from '../../lib/appManagers/utils/messageId/getServerMessageId';
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
import lottieLoader from '../../lib/rlottie/lottieLoader';
import RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
import rootScope from '../../lib/rootScope';
@ -35,9 +36,15 @@ import {SendMessageEmojiInteractionData} from '../../types'; @@ -35,9 +36,15 @@ import {SendMessageEmojiInteractionData} from '../../types';
import {getEmojiToneIndex} from '../../vendor/emoji';
import animationIntersector from '../animationIntersector';
import LazyLoadQueue from '../lazyLoadQueue';
import PopupStickers from '../popups/stickers';
import {hideToast, toastNew} from '../toast';
import wrapStickerAnimation from './stickerAnimation';
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}: {
// 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;
const EMOJI_EFFECT_MULTIPLIER = 3;
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}: {
doc: MyDocument,
div: HTMLElement,
middleware?: () => boolean,
@ -55,7 +62,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -55,7 +62,9 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
needUpscale?: boolean,
skipRatio?: number,
static?: boolean,
managers?: AppManagers
managers?: AppManagers,
fullThumb?: PhotoSize | VideoSize,
isOut?: boolean
}) {
const stickerType = doc.sticker;
if(stickerType === 1) {
@ -118,17 +127,23 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -118,17 +127,23 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
return cacheContext = await managers.thumbsStorage.getCacheContext(doc, type);
};
const isAnimated = !asStatic && (stickerType === 2 || stickerType === 3);
const effectThumb = getStickerEffectThumb(doc);
if(isOut !== undefined && effectThumb && !isOut) {
div.classList.add('reflect-x');
}
if(asStatic && stickerType !== 1) {
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
await getCacheContext(thumb.type);
} else {
await getCacheContext();
await getCacheContext(fullThumb?.type);
}
const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1;
const downloaded = cacheContext.downloaded && !needFadeIn;
const isAnimated = !asStatic && (stickerType === 2 || stickerType === 3);
const isThumbNeededForType = isAnimated;
const lottieCachedThumb = stickerType === 2 || stickerType === 3 ? await managers.appDocsManager.getLottieCachedThumb(doc.id, toneIndex) : undefined;
@ -293,7 +308,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -293,7 +308,7 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
// appDocsManager.downloadDocNew(doc.id).promise.then((res) => res.json()).then(async(json) => {
// fetch(doc.url).then((res) => res.json()).then(async(json) => {
return await appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId})
return await appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId, thumb: fullThumb})
.then(async(blob) => {
// console.timeEnd('download sticker' + doc.id);
// console.log('loaded sticker:', doc, div/* , blob */);
@ -406,24 +421,18 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -406,24 +421,18 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
return;
}
const bubble = findUpClassName(div, 'bubble');
const isOut = bubble.classList.contains('is-out');
const {animationDiv} = wrapStickerAnimation({
doc,
middleware,
side: isOut ? 'right' : 'left',
size: 280,
target: div,
play: true
play: true,
withRandomOffset: true
});
if(bubble) {
if(isOut) {
animationDiv.classList.add('is-out');
} else {
animationDiv.classList.add('is-in');
}
if(isOut !== undefined && !isOut) {
animationDiv.classList.add('reflect-x');
}
if(!sendInteractionThrottled) {
@ -464,6 +473,54 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue, @@ -464,6 +473,54 @@ export default async function wrapSticker({doc, div, middleware, lazyLoadQueue,
sendInteractionThrottled();
}
});
} else if(effectThumb && isOut !== undefined) {
managers.appStickersManager.preloadSticker(doc.id, true);
let playing = false;
attachClickEvent(div, async(e) => {
cancelEvent(e);
if(playing) {
const a = document.createElement('a');
a.onclick = () => {
hideToast();
new PopupStickers(doc.stickerSetInput).show();
};
toastNew({
langPackKey: 'Sticker.Premium.Click.Info',
langPackArguments: [a]
});
return;
}
playing = true;
const {animationDiv, stickerPromise} = wrapStickerAnimation({
doc,
middleware,
side: isOut ? 'right' : 'left',
size: width * STICKER_EFFECT_MULTIPLIER,
target: div,
play: true,
fullThumb: effectThumb
});
if(isOut !== undefined && !isOut) {
animationDiv.classList.add('reflect-x');
}
stickerPromise.then((player) => {
const onFrame = (frameNo: number) => {
if(frameNo === player.maxFrame) {
playing = false;
player.removeEventListener('enterFrame', onFrame);
}
};
player.addEventListener('enterFrame', onFrame);
});
});
}
return animation;

18
src/components/wrappers/stickerAnimation.ts

@ -8,6 +8,7 @@ import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport'; @@ -8,6 +8,7 @@ import IS_VIBRATE_SUPPORTED from '../../environment/vibrateSupport';
import assumeType from '../../helpers/assumeType';
import isInDOM from '../../helpers/dom/isInDOM';
import throttleWithRaf from '../../helpers/schedulers/throttleWithRaf';
import {PhotoSize, VideoSize} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager';
import appImManager from '../../lib/appManagers/appImManager';
import {AppManagers} from '../../lib/appManagers/managers';
@ -22,7 +23,9 @@ export default function wrapStickerAnimation({ @@ -22,7 +23,9 @@ export default function wrapStickerAnimation({
side,
skipRatio,
play,
managers
managers,
fullThumb,
withRandomOffset
}: {
size: number,
doc: MyDocument,
@ -31,7 +34,9 @@ export default function wrapStickerAnimation({ @@ -31,7 +34,9 @@ export default function wrapStickerAnimation({
side: 'left' | 'center' | 'right',
skipRatio?: number,
play: boolean,
managers?: AppManagers
managers?: AppManagers,
fullThumb?: PhotoSize | VideoSize,
withRandomOffset?: boolean
}) {
const animationDiv = document.createElement('div');
animationDiv.classList.add('emoji-animation');
@ -52,7 +57,8 @@ export default function wrapStickerAnimation({ @@ -52,7 +57,8 @@ export default function wrapStickerAnimation({
play,
group: 'none',
skipRatio,
managers
managers,
fullThumb
}).then(({render}) => render).then((animation) => {
assumeType<RLottiePlayer>(animation);
animation.addEventListener('enterFrame', (frameNo) => {
@ -77,9 +83,9 @@ export default function wrapStickerAnimation({ @@ -77,9 +83,9 @@ export default function wrapStickerAnimation({
return r > max ? -r % max : r;
};
const randomOffsetX = generateRandomSigned(16);
const randomOffsetY = generateRandomSigned(4);
const stableOffsetX = size / 8 * (side === 'right' ? 1 : -1);
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)) {
return;

117
src/helpers/contextMenuController.ts

@ -4,27 +4,17 @@ @@ -4,27 +4,17 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import appNavigationController from '../components/appNavigationController';
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import {IS_MOBILE_SAFARI} from '../environment/userAgent';
import cancelEvent from './dom/cancelEvent';
import {CLICK_EVENT_NAME} from './dom/clickEvent';
import EventListenerBase from './eventListenerBase';
import mediaSizes from './mediaSizes';
import OverlayClickHandler from './overlayClickHandler';
class ContextMenuController extends EventListenerBase<{
toggle: (open: boolean) => void
}> {
private openedMenu: HTMLElement;
private menuOverlay: HTMLElement;
private openedMenuOnClose: () => void;
class ContextMenuController extends OverlayClickHandler {
constructor() {
super();
super('menu', true);
mediaSizes.addEventListener('resize', () => {
if(this.openedMenu) {
this.closeBtnMenu();
if(this.element) {
this.close();
}
/* if(openedMenu && (openedMenu.style.top || openedMenu.style.left)) {
@ -33,118 +23,53 @@ class ContextMenuController extends EventListenerBase<{ @@ -33,118 +23,53 @@ class ContextMenuController extends EventListenerBase<{
console.log(innerWidth, innerHeight, rect);
} */
})
});
}
public isOpened() {
return !!this.openedMenu;
return !!this.element;
}
private onMouseMove = (e: MouseEvent) => {
const rect = this.openedMenu.getBoundingClientRect();
const rect = this.element.getBoundingClientRect();
const {clientX, clientY} = e;
const diffX = clientX >= rect.right ? clientX - rect.right : rect.left - clientX;
const diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
this.closeBtnMenu();
this.close();
// openedMenu.parentElement.click();
}
// console.log('mousemove', diffX, diffY);
};
private onClick = (e: MouseEvent | TouchEvent) => {
// cancelEvent(e);
this.closeBtnMenu();
};
// ! no need in this due to the same handler in appNavigationController
/* const onKeyDown = (e: KeyboardEvent) => {
if(e.key === 'Escape') {
closeBtnMenu();
cancelEvent(e);
public close() {
if(this.element) {
this.element.classList.remove('active');
this.element.parentElement.classList.remove('menu-open');
}
}; */
public closeBtnMenu = () => {
if(this.openedMenu) {
this.openedMenu.classList.remove('active');
this.openedMenu.parentElement.classList.remove('menu-open');
// openedMenu.previousElementSibling.remove(); // remove overlay
if(this.menuOverlay) this.menuOverlay.remove();
this.openedMenu = undefined;
this.dispatchEvent('toggle', false);
}
if(this.openedMenuOnClose) {
this.openedMenuOnClose();
this.openedMenuOnClose = undefined;
}
super.close();
if(!IS_TOUCH_SUPPORTED) {
window.removeEventListener('mousemove', this.onMouseMove);
// window.removeEventListener('keydown', onKeyDown, {capture: true});
window.removeEventListener('contextmenu', this.onClick);
}
document.removeEventListener(CLICK_EVENT_NAME, this.onClick);
if(!IS_MOBILE_SAFARI) {
appNavigationController.removeByType('menu');
}
};
public openBtnMenu(menuElement: HTMLElement, onClose?: () => void) {
this.closeBtnMenu();
if(!IS_MOBILE_SAFARI) {
appNavigationController.pushItem({
type: 'menu',
onPop: (canAnimate) => {
this.closeBtnMenu();
}
});
}
}
this.openedMenu = menuElement;
this.openedMenu.classList.add('active');
this.openedMenu.parentElement.classList.add('menu-open');
public openBtnMenu(element: HTMLElement, onClose?: () => void) {
super.open(element);
if(!this.menuOverlay) {
this.menuOverlay = document.createElement('div');
this.menuOverlay.classList.add('btn-menu-overlay');
this.element.classList.add('active');
this.element.parentElement.classList.add('menu-open');
// ! because this event must be canceled, and can't cancel on menu click (below)
this.menuOverlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
this.onClick(e);
});
if(onClose) {
this.addEventListener('toggle', onClose, {once: true});
}
this.openedMenu.parentElement.insertBefore(this.menuOverlay, this.openedMenu);
// document.body.classList.add('disable-hover');
this.openedMenuOnClose = onClose;
if(!IS_TOUCH_SUPPORTED) {
window.addEventListener('mousemove', this.onMouseMove);
// window.addEventListener('keydown', onKeyDown, {capture: true});
window.addEventListener('contextmenu', this.onClick, {once: true});
}
/* // ! because this event must be canceled, and can't cancel on menu click (below)
overlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
onClick(e);
}); */
// ! safari iOS doesn't handle window click event on overlay, idk why
document.addEventListener(CLICK_EVENT_NAME, this.onClick);
this.dispatchEvent('toggle', true);
}
}

4
src/helpers/dom/clickEvent.ts

@ -45,11 +45,11 @@ export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* To @@ -45,11 +45,11 @@ export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* To
add(CLICK_EVENT_NAME, callback, options);
}
export function detachClickEvent(elem: HTMLElement, callback: (e: /* TouchEvent | */MouseEvent) => void, options?: AddEventListenerOptions) {
export function detachClickEvent(elem: HTMLElement | Window, callback: (e: /* TouchEvent | */MouseEvent) => void, options?: AddEventListenerOptions) {
// if(CLICK_EVENT_NAME === 'touchend') {
// elem.removeEventListener('touchstart', callback, options);
// } else {
elem.removeEventListener(CLICK_EVENT_NAME, callback, options);
elem.removeEventListener(CLICK_EVENT_NAME, callback as any, options);
// }
}

103
src/helpers/overlayClickHandler.ts

@ -0,0 +1,103 @@ @@ -0,0 +1,103 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import appNavigationController, {NavigationItem} from '../components/appNavigationController';
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import {IS_MOBILE_SAFARI} from '../environment/userAgent';
import cancelEvent from './dom/cancelEvent';
import {CLICK_EVENT_NAME} from './dom/clickEvent';
import findUpAsChild from './dom/findUpAsChild';
import EventListenerBase from './eventListenerBase';
export default class OverlayClickHandler extends EventListenerBase<{
toggle: (open: boolean) => void
}> {
protected element: HTMLElement;
protected overlay: HTMLElement;
protected listenerOptions: AddEventListenerOptions;
constructor(
protected navigationType: NavigationItem['type'],
protected withOverlay?: boolean
) {
super(false);
this.listenerOptions = withOverlay ? undefined : {capture: true};
}
protected onClick = (e: MouseEvent | TouchEvent) => {
if(this.element && findUpAsChild(e.target, this.element)) {
return;
}
cancelEvent(e);
this.close();
};
public close() {
if(this.element) {
this.overlay?.remove();
this.element = undefined;
this.dispatchEvent('toggle', false);
}
if(!IS_TOUCH_SUPPORTED) {
// window.removeEventListener('keydown', onKeyDown, {capture: true});
window.removeEventListener('contextmenu', this.onClick);
}
document.removeEventListener(CLICK_EVENT_NAME, this.onClick, this.listenerOptions);
if(!IS_MOBILE_SAFARI) {
appNavigationController.removeByType(this.navigationType);
}
}
public open(element: HTMLElement) {
this.close();
if(!IS_MOBILE_SAFARI) {
appNavigationController.pushItem({
type: this.navigationType,
onPop: (canAnimate) => {
this.close();
}
});
}
this.element = element;
if(!this.overlay && this.withOverlay) {
this.overlay = document.createElement('div');
this.overlay.classList.add('btn-menu-overlay');
// ! because this event must be canceled, and can't cancel on menu click (below)
this.overlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
this.onClick(e);
});
}
this.overlay && this.element.parentElement.insertBefore(this.overlay, this.element);
// document.body.classList.add('disable-hover');
if(!IS_TOUCH_SUPPORTED) {
// window.addEventListener('keydown', onKeyDown, {capture: true});
window.addEventListener('contextmenu', this.onClick, {once: true});
}
/* // ! because this event must be canceled, and can't cancel on menu click (below)
overlay.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
onClick(e);
}); */
// ! safari iOS doesn't handle window click event on overlay, idk why
document.addEventListener(CLICK_EVENT_NAME, this.onClick, this.listenerOptions);
this.dispatchEvent('toggle', true);
}
}

1
src/lang.ts

@ -1119,6 +1119,7 @@ const lang = { @@ -1119,6 +1119,7 @@ const lang = {
'Schedule.SendToday': 'Send today at %@',
'Schedule.SendDate': 'Send on %@ at %@',
'Schedule.SendWhenOnline': 'Send When Online',
'Sticker.Premium.Click.Info': 'This pack contains premium stickers like this one. [View Pack]()',
'Stickers.Recent': 'Recent',
// "Stickers.Favorite": "Favorite",
'StickerSet.DontExist': 'Sorry, this sticker set doesn\'t seem to exist.',

11
src/lib/appManagers/appStickersManager.ts

@ -249,7 +249,7 @@ export class AppStickersManager extends AppManager { @@ -249,7 +249,7 @@ export class AppStickersManager extends AppManager {
const id = isAnimation ? EMOJI_ANIMATIONS_SET_LOCAL_ID : EMOJI_SET_LOCAL_ID;
const stickerSet = this.storage.getFromCache(id);
// const stickerSet = await this.getStickerSet({id});
if(!stickerSet || !stickerSet.documents) return;
if(!stickerSet?.documents) return;
if(isAnimation) {
if(['🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎'].includes(emoji)) {
@ -293,14 +293,19 @@ export class AppStickersManager extends AppManager { @@ -293,14 +293,19 @@ export class AppStickersManager extends AppManager {
const sound = this.getAnimatedEmojiSoundDocument(emoji);
return Promise.all([
this.apiFileManager.downloadMedia({media: doc}),
sound ? this.apiFileManager.downloadMedia({media: sound}) : undefined
this.preloadSticker(doc.id),
sound ? this.preloadSticker(sound.id) : undefined
]).then(() => {
return {doc, sound};
});
});
}
public preloadSticker(docId: DocId, isPremiumEffect?: boolean) {
const doc = this.appDocsManager.getDoc(docId);
return this.apiFileManager.downloadMedia({media: doc, thumb: isPremiumEffect ? doc.video_thumbs?.[0] : undefined});
}
private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
const newSet: MessagesStickerSet = {
_: 'messages.stickerSet',

6
src/lib/appManagers/utils/docs/getDocumentDownloadOptions.ts

@ -4,15 +4,15 @@ @@ -4,15 +4,15 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type {Document, PhotoSize} from '../../../../layer';
import type {Document, PhotoSize, VideoSize} from '../../../../layer';
import type {DownloadOptions} from '../../../mtproto/apiFileManager';
import getDocumentInput from './getDocumentInput';
export default function getDocumentDownloadOptions(doc: Document.document, thumb?: PhotoSize.photoSize, queueId?: number, onlyCache?: boolean): DownloadOptions {
export default function getDocumentDownloadOptions(doc: Document.document, thumb?: PhotoSize.photoSize | VideoSize, queueId?: number, onlyCache?: boolean): DownloadOptions {
const inputFileLocation = getDocumentInput(doc, thumb?.type);
let mimeType: string;
if(thumb) {
if(thumb?._ === 'photoSize') {
mimeType = doc.sticker ? 'image/webp' : 'image/jpeg'/* doc.mime_type */;
} else {
mimeType = doc.mime_type || 'application/octet-stream';

2
src/lib/appManagers/utils/download/getDownloadMediaDetails.ts

@ -16,7 +16,7 @@ export default function getDownloadMediaDetails(options: DownloadMediaOptions) { @@ -16,7 +16,7 @@ export default function getDownloadMediaDetails(options: DownloadMediaOptions) {
let downloadOptions: DownloadOptions;
if(media._ === 'document') downloadOptions = getDocumentDownloadOptions(media, thumb as any, queueId, onlyCache);
else if(media._ === 'photo') downloadOptions = getPhotoDownloadOptions(media, thumb, queueId, onlyCache);
else if(media._ === 'photo') downloadOptions = getPhotoDownloadOptions(media, thumb as any, queueId, onlyCache);
else if(isWebDocument(media)) downloadOptions = getWebDocumentDownloadOptions(media);
downloadOptions.downloadId = options.downloadId;

5
src/lib/appManagers/utils/stickers/getStickerEffectThumb.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import {MyDocument} from '../../appDocsManager';
export default function getStickerEffectThumb(doc: MyDocument) {
return doc.video_thumbs?.[0];
}

4
src/lib/mtproto/apiFileManager.ts

@ -13,7 +13,7 @@ import type {ReferenceBytes} from './referenceDatabase'; @@ -13,7 +13,7 @@ import type {ReferenceBytes} from './referenceDatabase';
import Modes from '../../config/modes';
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
import {randomLong} from '../../helpers/random';
import {Document, InputFile, InputFileLocation, InputWebFileLocation, Photo, PhotoSize, UploadFile, UploadWebFile, WebDocument} from '../../layer';
import {Document, InputFile, InputFileLocation, InputWebFileLocation, Photo, PhotoSize, UploadFile, UploadWebFile, VideoSize, WebDocument} from '../../layer';
import {DcId} from '../../types';
import CacheStorageController from '../files/cacheStorage';
import {logger, LogTypes} from '../logger';
@ -62,7 +62,7 @@ export type DownloadOptions = { @@ -62,7 +62,7 @@ export type DownloadOptions = {
export type DownloadMediaOptions = {
media: Photo.photo | Document.document | WebDocument,
thumb?: PhotoSize,
thumb?: PhotoSize | VideoSize,
queueId?: number,
onlyCache?: boolean,
downloadId?: string

4
src/scss/components/_global.scss

@ -177,6 +177,10 @@ Utility Classes @@ -177,6 +177,10 @@ Utility Classes
pointer-events: none !important;
}
.reflect-x {
transform: scaleX(-1);
}
/* .flex-grow {
flex-grow: 1;
}

6
src/scss/partials/_emojiAnimation.scss

@ -10,12 +10,6 @@ @@ -10,12 +10,6 @@
// @include sidebar-transform(true);
&.is-in {
.rlottie {
transform: scaleX(-1);
}
}
&-container {
position: absolute;
top: 0;

18
src/scss/style.scss

@ -699,13 +699,27 @@ input:-webkit-autofill:active { @@ -699,13 +699,27 @@ input:-webkit-autofill:active {
color: #fff;
font-size: 1rem;
border-radius: $border-radius-medium;
animation: fade-in-opacity-fade-out-opacity 3s linear forwards;
z-index: 5;
max-width: 22.5rem;
max-width: unquote('min(30rem, calc(100vw - 2rem))');
opacity: 0;
backdrop-filter: blur(25px);
&.is-visible {
opacity: 1;
}
@include animation-level(2) {
transition: opacity var(--transition-standard-in);
}
b {
color: inherit;
}
a {
color: #60a5e9!important;
cursor: pointer;
}
}
hr {

Loading…
Cancel
Save