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.

281 lines
8.4 KiB

3 years ago
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
3 years ago
import { logger, LogTypes } from "../lib/logger";
import fastSmoothScroll, { ScrollOptions } from "../helpers/fastSmoothScroll";
3 years ago
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import { cancelEvent } from "../helpers/dom/cancelEvent";
/*
var el = $0;
var height = 0;
var checkUp = false;
do {
height += el.scrollHeight;
} while(el = (checkUp ? el.previousElementSibling : el.nextElementSibling));
console.log(height);
*/
/*
Array.from($0.querySelectorAll('.bubble-content')).forEach(_el => {
//_el.style.display = '';
//return;
let el = _el.parentElement;
let height = el.scrollHeight;
let width = el.scrollWidth;
el.style.width = width + 'px';
el.style.height = height + 'px';
_el.style.display = 'none';
});
*/
/* const scrollables: Map<HTMLElement, Scrollable> = new Map();
const scrollsIntersector = new IntersectionObserver(entries => {
for(let entry of entries) {
const scrollable = scrollables.get(entry.target as HTMLElement);
if(entry.isIntersecting) {
scrollable.isVisible = true;
} else {
scrollable.isVisible = false;
if(!isInDOM(entry.target)) {
scrollsIntersector.unobserve(scrollable.container);
scrollables.delete(scrollable.container);
}
}
}
}); */
export class ScrollableBase {
protected log: ReturnType<typeof logger>;
3 years ago
public splitUp: HTMLElement;
3 years ago
public onScrollMeasure: number = 0;
3 years ago
public lastScrollPosition: number = 0;
public lastScrollDirection: number = 0;
public onAdditionalScroll: () => void;
public onScrolledTop: () => void;
public onScrolledBottom: () => void;
3 years ago
public isHeavyAnimationInProgress = false;
protected needCheckAfterAnimation = false;
3 years ago
public checkForTriggers?: () => void;
public scrollProperty: 'scrollTop' | 'scrollLeft';
private removeHeavyAnimationListener: () => void;
3 years ago
constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
this.container.classList.add('scrollable');
this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''), LogTypes.Error);
if(el) {
Array.from(el.children).forEach(c => this.container.append(c));
el.append(this.container);
}
//this.onScroll();
}
3 years ago
public setListeners() {
if(this.removeHeavyAnimationListener) {
return;
}
3 years ago
window.addEventListener('resize', this.onScroll, {passive: true});
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
3 years ago
this.removeHeavyAnimationListener = useHeavyAnimationCheck(() => {
3 years ago
this.isHeavyAnimationInProgress = true;
if(this.onScrollMeasure) {
this.needCheckAfterAnimation = true;
window.cancelAnimationFrame(this.onScrollMeasure);
}
}, () => {
this.isHeavyAnimationInProgress = false;
if(this.needCheckAfterAnimation) {
this.onScroll();
this.needCheckAfterAnimation = false;
}
});
}
3 years ago
public removeListeners() {
if(!this.removeHeavyAnimationListener) {
return;
}
window.removeEventListener('resize', this.onScroll);
this.container.removeEventListener('scroll', this.onScroll, {capture: true});
this.removeHeavyAnimationListener();
}
3 years ago
public append(element: HTMLElement) {
this.container.append(element);
}
public scrollIntoViewNew(options: Omit<ScrollOptions, 'container'>) {
3 years ago
//return Promise.resolve();
return fastSmoothScroll({
...options,
container: this.container
});
3 years ago
}
public onScroll = () => {
//if(this.debug) {
//this.log('onScroll call', this.onScrollMeasure);
//}
//return;
if(this.isHeavyAnimationInProgress) {
if(this.onScrollMeasure) {
window.cancelAnimationFrame(this.onScrollMeasure);
}
this.needCheckAfterAnimation = true;
return;
}
//if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return;
if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
this.onScrollMeasure = window.requestAnimationFrame(() => {
this.onScrollMeasure = 0;
3 years ago
const scrollPosition = this.container[this.scrollProperty];
this.lastScrollDirection = this.lastScrollPosition === scrollPosition ? 0 : (this.lastScrollPosition < scrollPosition ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollPosition = scrollPosition;
3 years ago
if(this.onAdditionalScroll && this.lastScrollDirection !== 0) {
this.onAdditionalScroll();
}
if(this.checkForTriggers) {
this.checkForTriggers();
}
});
};
3 years ago
}
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);
}
3 years ago
public checkForTriggers = () => {
if((!this.onScrolledTop && !this.onScrolledBottom)) return;
if(this.isHeavyAnimationInProgress) {
this.onScroll();
return;
}
const scrollHeight = this.container.scrollHeight;
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
return;
}
const clientHeight = this.container.clientHeight;
const maxScrollTop = scrollHeight - clientHeight;
3 years ago
const scrollTop = this.lastScrollPosition;
3 years ago
//this.log('checkForTriggers:', scrollTop, maxScrollTop);
if(this.onScrolledTop && scrollTop <= this.onScrollOffset && this.lastScrollDirection <= 0/* && direction === -1 */) {
this.onScrolledTop();
}
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && this.lastScrollDirection >= 0/* && direction === 1 */) {
this.onScrolledBottom();
}
};
public prepend(...elements: (HTMLElement | DocumentFragment)[]) {
3 years ago
(this.splitUp || this.padding || this.container).prepend(...elements);
}
public append(...elements: (HTMLElement | DocumentFragment)[]) {
3 years ago
(this.splitUp || this.padding || this.container).append(...elements);
}
public getDistanceToEnd() {
return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight);
}
get isScrolledDown() {
return this.getDistanceToEnd() <= 1;
}
set scrollTop(y: number) {
this.container.scrollTop = y;
}
get scrollTop() {
//this.log.trace('get scrollTop');
return this.container.scrollTop;
}
get scrollHeight() {
return this.container.scrollHeight;
}
}
export class ScrollableX extends ScrollableBase {
constructor(el: HTMLElement, logPrefix = '', public onScrollOffset = 300, public splitCount = 15, public container: HTMLElement = document.createElement('div')) {
super(el, logPrefix, container);
this.container.classList.add('scrollable-x');
if(!IS_TOUCH_SUPPORTED) {
3 years ago
const scrollHorizontally = (e: any) => {
if(!e.deltaX && this.container.scrollWidth > this.container.clientWidth) {
this.container.scrollLeft += e.deltaY / 4;
cancelEvent(e);
}
};
this.container.addEventListener('wheel', scrollHorizontally, {passive: false});
}
3 years ago
this.scrollProperty = 'scrollLeft';
3 years ago
}
}