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

218 lines
6.7 KiB

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import findUpClassName from '../helpers/dom/findUpClassName';
import sequentialDom from '../helpers/sequentialDom';
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import rootScope from '../lib/rootScope';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import {fastRaf} from '../helpers/schedulers';
import liteMode from '../helpers/liteMode';
let rippleClickId = 0;
export default function ripple(
elem: HTMLElement,
callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(),
onEnd: (id: number) => void = null,
prepend = false,
attachListenerTo = elem
) {
// return;
if(elem.querySelector('.c-ripple')) return;
elem.classList.add('rp');
const r = document.createElement('div');
r.classList.add('c-ripple');
const isSquare = elem.classList.contains('rp-square');
if(isSquare) {
r.classList.add('is-square');
}
elem[prepend ? 'prepend' : 'append'](r);
let handler: () => void;
// let animationEndPromise: Promise<number>;
const drawRipple = (clientX: number, clientY: number) => {
const startTime = Date.now();
const circle = document.createElement('div');
const clickId = rippleClickId++;
// console.log('ripple drawRipple');
// const auto = elem.classList.contains('row-sortable') && !elem.classList.contains('cant-sort');
const auto = false;
const duration = (auto ? .3 : +window.getComputedStyle(r).getPropertyValue('--ripple-duration').replace('s', '')) * 1000;
// console.log('ripple duration', duration);
const _handler = handler = () => {
// handler = () => animationEndPromise.then((duration) => {
// console.log('ripple animation was:', duration);
// const duration = isSquare || mediaSizes.isMobile ? 200 : 700;
// return;
const elapsedTime = Date.now() - startTime;
const cb = () => {
// console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
sequentialDom.mutate(() => {
circle.remove();
});
onEnd?.(clickId);
};
if(elapsedTime < duration) {
const delay = Math.max(duration - elapsedTime, duration / 2);
setTimeout(() => circle.classList.add('hiding'), Math.max(delay - duration / 2, 0));
setTimeout(cb, delay);
} else {
circle.classList.add('hiding');
setTimeout(cb, duration / 2);
}
if(!IS_TOUCH_SUPPORTED) {
window.removeEventListener('contextmenu', handler);
window.removeEventListener('mousemove', handler);
}
handler = null;
touchStartFired = false;
};
// });
callback?.(clickId);
/* callback().then((bad) => {
if(bad) {
span.remove();
return;
} */
// console.log('ripple after promise', Date.now() - startTime);
// console.log('ripple tooSlow:', tooSlow);
/* if(tooSlow) {
span.remove();
return;
} */
fastRaf(() => {
if(_handler !== handler) {
return;
}
const rect = r.getBoundingClientRect();
circle.classList.add('c-ripple__circle');
const clickX = clientX - rect.left;
const clickY = clientY - rect.top;
const radius = Math.sqrt((Math.abs(clickY - rect.height / 2) + rect.height / 2) ** 2 + (Math.abs(clickX - rect.width / 2) + rect.width / 2) ** 2);
const size = radius;
// center of circle
const x = clickX - size / 2;
const y = clickY - size / 2;
// console.log('ripple click', offsetFromCenter, size, clickX, clickY);
circle.style.width = circle.style.height = size + 'px';
circle.style.left = x + 'px';
circle.style.top = y + 'px';
// нижний код выполняется с задержкой
/* animationEndPromise = new Promise((resolve) => {
span.addEventListener('animationend', () => {
// 713 -> 700
resolve(((Date.now() - startTime) / 100 | 0) * 100);
}, {once: true});
}); */
// нижний код не всегда включает анимацию ПРИ КЛИКЕ НА ТАЧПАД БЕЗ ТАПТИК ЭНЖИНА
/* span.style.display = 'none';
r.append(span);
duration = +window.getComputedStyle(span).getPropertyValue('animation-duration').replace('s', '') * 1000;
span.style.display = ''; */
r.append(circle);
if(auto) {
// window.addEventListener('mousemove', handler, {once: true, passive: true});
handler();
}
// r.classList.add('active');
// handler();
});
// });
};
const isRippleUnneeded = (e: Event) => {
return e.target !== elem && (
['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) ||
findUpClassName(e.target as HTMLElement, 'c-ripple') !== r
) && (
attachListenerTo === elem ||
!findUpAsChild(e.target as HTMLElement, attachListenerTo)
) && !findUpClassName(e.target, 'checkbox-field');
};
// TODO: rename this variable
let touchStartFired = false;
if(IS_TOUCH_SUPPORTED) {
const touchEnd = () => {
handler?.();
};
attachListenerTo.addEventListener('touchstart', (e) => {
if(!liteMode.isAvailable('animations')) {
return;
}
// console.log('ripple touchstart', e);
if(e.touches.length > 1 || touchStartFired || isRippleUnneeded(e)) {
return;
}
// console.log('touchstart', e);
touchStartFired = true;
const {clientX, clientY} = e.touches[0];
drawRipple(clientX, clientY);
attachListenerTo.addEventListener('touchend', touchEnd, {once: true});
window.addEventListener('touchmove', (e) => {
e.cancelBubble = true;
e.stopPropagation();
touchEnd();
attachListenerTo.removeEventListener('touchend', touchEnd);
}, {once: true});
}, {passive: true});
} else {
attachListenerTo.addEventListener('mousedown', (e) => {
if(![0, 2].includes(e.button)) { // only left and right buttons
return;
}
if(!liteMode.isAvailable('animations')) {
return;
}
// console.log('ripple mousedown', e, e.target, findUpClassName(e.target as HTMLElement, 'c-ripple') === r);
if(attachListenerTo.dataset.ripple === '0' || isRippleUnneeded(e)) {
return;
} else if(touchStartFired) {
touchStartFired = false;
return;
}
const {clientX, clientY} = e;
drawRipple(clientX, clientY);
window.addEventListener('mouseup', handler, {once: true, passive: true});
window.addEventListener('contextmenu', handler, {once: true, passive: true});
}, {passive: true});
}
}