Stickers: premium effect & toast
This commit is contained in:
parent
dfcc4d11a5
commit
0c4a99f67d
@ -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,
|
||||
|
@ -62,7 +62,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
|
||||
}
|
||||
|
||||
if(!keepOpen) {
|
||||
contextMenuController.closeBtnMenu();
|
||||
contextMenuController.close();
|
||||
}
|
||||
|
||||
if(checkboxField && !noCheckboxClickListener/* && result !== false */) {
|
||||
|
@ -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 = () => {
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}, {
|
||||
|
@ -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 {
|
||||
appendTo = isFull ? PopupElement.getPopups(PopupGroupCall)[0].getContainer(): document.body;
|
||||
|
||||
if(!isFull) {
|
||||
contextMenuController.closeBtnMenu();
|
||||
contextMenuController.close();
|
||||
}
|
||||
}, listenerSetter);
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
||||
if(toastEl.dataset.timeout) clearTimeout(+toastEl.dataset.timeout);
|
||||
toastEl.dataset.timeout = '' + setTimeout(() => {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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';
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
|
||||
// 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,
|
||||
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,
|
||||
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;
|
||||
|
@ -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({
|
||||
side,
|
||||
skipRatio,
|
||||
play,
|
||||
managers
|
||||
managers,
|
||||
fullThumb,
|
||||
withRandomOffset
|
||||
}: {
|
||||
size: number,
|
||||
doc: MyDocument,
|
||||
@ -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({
|
||||
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({
|
||||
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;
|
||||
|
@ -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<{
|
||||
|
||||
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 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);
|
||||
public close() {
|
||||
if(this.element) {
|
||||
this.element.classList.remove('active');
|
||||
this.element.parentElement.classList.remove('menu-open');
|
||||
}
|
||||
|
||||
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);
|
||||
public openBtnMenu(element: HTMLElement, onClose?: () => void) {
|
||||
super.open(element);
|
||||
|
||||
if(!IS_MOBILE_SAFARI) {
|
||||
appNavigationController.removeByType('menu');
|
||||
this.element.classList.add('active');
|
||||
this.element.parentElement.classList.add('menu-open');
|
||||
|
||||
if(onClose) {
|
||||
this.addEventListener('toggle', onClose, {once: true});
|
||||
}
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
if(!this.menuOverlay) {
|
||||
this.menuOverlay = document.createElement('div');
|
||||
this.menuOverlay.classList.add('btn-menu-overlay');
|
||||
|
||||
// ! 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);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
Normal file
103
src/helpers/overlayClickHandler.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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.',
|
||||
|
@ -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 {
|
||||
|
||||
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',
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,5 @@
|
||||
import {MyDocument} from '../../appDocsManager';
|
||||
|
||||
export default function getStickerEffectThumb(doc: MyDocument) {
|
||||
return doc.video_thumbs?.[0];
|
||||
}
|
@ -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 = {
|
||||
|
||||
export type DownloadMediaOptions = {
|
||||
media: Photo.photo | Document.document | WebDocument,
|
||||
thumb?: PhotoSize,
|
||||
thumb?: PhotoSize | VideoSize,
|
||||
queueId?: number,
|
||||
onlyCache?: boolean,
|
||||
downloadId?: string
|
||||
|
@ -177,6 +177,10 @@ Utility Classes
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.reflect-x {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* .flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
|
||||
// @include sidebar-transform(true);
|
||||
|
||||
&.is-in {
|
||||
.rlottie {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
&-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -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…
Reference in New Issue
Block a user