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.
280 lines
8.4 KiB
280 lines
8.4 KiB
/* |
|
* 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"; |
|
import { logger, LogTypes } from "../lib/logger"; |
|
import fastSmoothScroll, { ScrollOptions } from "../helpers/fastSmoothScroll"; |
|
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>; |
|
|
|
public splitUp: HTMLElement; |
|
public onScrollMeasure: number = 0; |
|
|
|
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'); |
|
|
|
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(); |
|
} |
|
|
|
public setListeners() { |
|
if(this.removeHeavyAnimationListener) { |
|
return; |
|
} |
|
|
|
window.addEventListener('resize', this.onScroll, {passive: true}); |
|
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true}); |
|
|
|
this.removeHeavyAnimationListener = useHeavyAnimationCheck(() => { |
|
this.isHeavyAnimationInProgress = true; |
|
|
|
if(this.onScrollMeasure) { |
|
this.needCheckAfterAnimation = true; |
|
window.cancelAnimationFrame(this.onScrollMeasure); |
|
} |
|
}, () => { |
|
this.isHeavyAnimationInProgress = false; |
|
|
|
if(this.needCheckAfterAnimation) { |
|
this.onScroll(); |
|
this.needCheckAfterAnimation = false; |
|
} |
|
}); |
|
} |
|
|
|
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); |
|
} |
|
|
|
public scrollIntoViewNew(options: Omit<ScrollOptions, 'container'>) { |
|
//return Promise.resolve(); |
|
return fastSmoothScroll({ |
|
...options, |
|
container: this.container |
|
}); |
|
} |
|
|
|
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; |
|
|
|
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(); |
|
} |
|
|
|
if(this.checkForTriggers) { |
|
this.checkForTriggers(); |
|
} |
|
}); |
|
}; |
|
} |
|
|
|
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; |
|
|
|
if(this.isHeavyAnimationInProgress) { |
|
this.onScroll(); |
|
return; |
|
} |
|
|
|
const scrollHeight = this.container.scrollHeight; |
|
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден |
|
return; |
|
} |
|
|
|
const clientHeight = this.container.clientHeight; |
|
const maxScrollTop = scrollHeight - clientHeight; |
|
const scrollTop = this.lastScrollPosition; |
|
|
|
//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)[]) { |
|
(this.splitUp || this.padding || this.container).prepend(...elements); |
|
} |
|
|
|
public append(...elements: (HTMLElement | DocumentFragment)[]) { |
|
(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) { |
|
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}); |
|
} |
|
|
|
this.scrollProperty = 'scrollLeft'; |
|
} |
|
}
|
|
|