Browse Source

check that (new virtual scroll)

master
Eduard Kuzmenko 4 years ago
parent
commit
8873c2d3ec
  1. 15
      package-lock.json
  2. 1
      package.json
  3. 2
      src/components/pageIm.ts
  4. 326
      src/components/scrollable.ts
  5. 428
      src/components/scrollable_good.ts
  6. 9
      src/components/wrappers.ts
  7. 22
      src/lib/appManagers/appDialogsManager.ts
  8. 30
      src/lib/appManagers/appImManager.ts
  9. 23
      src/lib/appManagers/appPhotosManager.ts
  10. 29
      src/lib/appManagers/appSidebarLeft.ts
  11. 4
      src/lib/services.ts
  12. 4
      src/scss/partials/_chatlist.scss

15
package-lock.json generated

@ -4004,6 +4004,15 @@ @@ -4004,6 +4004,15 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fastdom": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.9.tgz",
"integrity": "sha512-SSp4fbVzu8JkkG01NUX+0iOwe9M5PN3MGIQ84txLf4TkkJG4q30khkzumKgi4hUqO1+jX6wLHfnCPoZ6eSZ6Tg==",
"dev": true,
"requires": {
"strictdom": "^1.0.1"
}
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@ -12823,6 +12832,12 @@ @@ -12823,6 +12832,12 @@
"integrity": "sha512-IpXeZ67YxcsrfZHe3yg/IyZ5KPfRSn1teDy5mRX2e8M6K410NcJNcR+SFQ2Z92DO36VBUArQP4Vy3Qu33MwIOQ==",
"dev": true
},
"strictdom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz",
"integrity": "sha1-GJ3pFkn3PUTVm4Qy76aO+dJllGA=",
"dev": true
},
"string-length": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",

1
package.json

@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
"babel-jest": "^24.9.0",
"compression-webpack-plugin": "^3.1.0",
"css-loader": "^3.2.0",
"fastdom": "^1.0.9",
"file-loader": "^4.3.0",
"html-webpack-plugin": "^3.2.0",
"install": "^0.13.0",

2
src/components/pageIm.ts

@ -1,8 +1,6 @@ @@ -1,8 +1,6 @@
//import { appImManager, appMessagesManager, appDialogsManager, apiUpdatesManager, appUsersManager } from "../lib/services";
import { openBtnMenu } from "./misc";
import {stackBlurImage} from '../lib/StackBlur';
import appSidebarLeft from "../lib/appManagers/appSidebarLeft";
export default () => import('../lib/services').then(services => {

326
src/components/scrollable.ts

@ -1,4 +1,19 @@ @@ -1,4 +1,19 @@
import { isElementInViewport, isScrolledIntoView, cancelEvent } from "../lib/utils";
import { cancelEvent } from "../lib/utils";
//import {measure} from 'fastdom/fastdom.min';
import FastDom from 'fastdom';
import 'fastdom/src/fastdom-strict'; // exclude in production
import FastDomPromised from 'fastdom/extensions/fastdom-promised';
//const fastdom = FastDom.extend(FastDomPromised);
const fastdom = ((window as any).fastdom as typeof FastDom).extend(FastDomPromised);
(window as any).fastdom.strict(false);
setTimeout(() => {
//(window as any).fastdom.strict(true);
}, 5e3);
export default class Scrollable {
public container: HTMLDivElement;
@ -27,7 +42,6 @@ export default class Scrollable { @@ -27,7 +42,6 @@ export default class Scrollable {
public paddingBottomDiv: HTMLDivElement;
public splitUp: HTMLElement;
public splitOffset = 0;
public onAddedBottom: () => void = null;
@ -37,8 +51,10 @@ export default class Scrollable { @@ -37,8 +51,10 @@ export default class Scrollable {
public isBottomIntersecting: boolean;
public splitObserver: IntersectionObserver;
public splitMeasure: Promise<any> = null;
public splitMutate: Promise<any> = null;
constructor(public el: HTMLDivElement, x = false, y = true) {
constructor(public el: HTMLDivElement, x = false, y = true, public splitOffset = 300) {
this.container = document.createElement('div');
this.container.classList.add('scrollable');
@ -47,7 +63,7 @@ export default class Scrollable { @@ -47,7 +63,7 @@ export default class Scrollable {
this.topObserver = new IntersectionObserver(entries => {
let entry = entries[0];
// console.log('top intersection:', entries, this.isTopIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
console.log('top intersection:', entries, this.isTopIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isTopIntersecting = entry.isIntersecting) {
this.onTopIntersection(entry);
}
@ -57,7 +73,7 @@ export default class Scrollable { @@ -57,7 +73,7 @@ export default class Scrollable {
this.bottomObserver = new IntersectionObserver(entries => {
let entry = entries[0];
// console.log('bottom intersection:', entries, this.isBottomIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
console.log('bottom intersection:', entries, this.isBottomIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isBottomIntersecting = entry.isIntersecting) {
this.onBottomIntersection(entry);
@ -66,48 +82,89 @@ export default class Scrollable { @@ -66,48 +82,89 @@ export default class Scrollable {
}, {threshold: arr});
this.splitObserver = new IntersectionObserver(entries => {
//console.log('splitObserver', entries);
for(let entry of entries) {
console.log('splitObserver', entries);
for(let entry of entries) { // there may be duplicates (1st - not intersecting, 2nd - intersecting)
//console.log('onscroll entry', entry.target, entry.isIntersecting, entry);
if(!entry.isIntersecting && entry.target.parentElement) {
if(!entry.isIntersecting && entry.target.parentElement && entry.rootBounds) {
let child = entry.target;
//console.log('onscroll entry', entry.boundingClientRect, child, entry);
let isTop = (entry.boundingClientRect.top + this.splitOffset) <= 0;
let isTop = entry.boundingClientRect.top <= 0;
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
let needHeight = this.splitOffset;
//console.log('will call measure');
if(isTop) {
let sliced: Element[] = [child];
while(child.previousElementSibling) {
sliced.push(child = child.previousElementSibling);
}
sliced.reverse();
sliced.forEach(child => {
let height = child.scrollHeight;
this.paddings.up += height;
this.hiddenElements.up.push({element: child, height});
child.parentElement.removeChild(child);
this.paddingTopDiv.style.height = this.paddings.up + 'px';
this.onBottomIntersection(entry);
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
this.splitMeasure = fastdom.measure(() => {
let sliced: {element: Element, height: number}[] = [/* child */];
do {
if(needHeight > 0) {
needHeight -= child.scrollHeight;
} else {
sliced.push({element: child, height: child.scrollHeight});
}
} while(child = child.previousElementSibling);
return sliced;
});
this.splitMeasure.then(sliced => {
if(this.splitMutate) fastdom.clear(this.splitMutate);
this.splitMutate = fastdom.mutate(() => {
let length = sliced.length;
for(let i = length - 1; i >= 0; --i) {
let {element, height} = sliced[i];
if(!this.splitUp.contains(element)) continue;
this.paddings.up += height;
this.hiddenElements.up.push(sliced[i]);
this.splitUp.removeChild(element);
//element.parentElement.removeChild(element);
}
this.paddingTopDiv.style.height = this.paddings.up + 'px';
});
});
//console.log('onscroll sliced up', sliced);
} else if(isBottom) {
let sliced: Element[] = [child];
while(child.nextElementSibling) {
sliced.push(child = child.nextElementSibling);
}
sliced.reverse();
sliced.forEach(child => {
let height = child.scrollHeight;
this.paddings.down += height;
this.hiddenElements.down.unshift({element: child, height});
child.parentElement.removeChild(child);
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
this.onTopIntersection(entry);
if(this.splitMeasure) fastdom.clear(this.splitMeasure);
this.splitMeasure = fastdom.measure(() => {
let sliced: {element: Element, height: number}[] = [/* child */];
do {
if(needHeight > 0) {
needHeight -= child.scrollHeight;
} else {
sliced.push({element: child, height: child.scrollHeight});
}
} while(child = child.nextElementSibling);
return sliced;
});
this.splitMeasure.then(sliced => {
if(this.splitMutate) fastdom.clear(this.splitMutate);
this.splitMutate = fastdom.mutate(() => {
let length = sliced.length;
for(let i = length - 1; i >= 0; --i) {
let {element, height} = sliced[i];
if(!this.splitUp.contains(element)) continue;
this.paddings.down += height;
this.hiddenElements.down.unshift(sliced[i]);
this.splitUp.removeChild(element);
//element.parentElement.removeChild(element);
}
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
});
});
//console.log('onscroll sliced down', sliced);
@ -159,6 +216,7 @@ export default class Scrollable { @@ -159,6 +216,7 @@ export default class Scrollable {
// @ts-ignore
this.thumb.style[this.type] = '30px';
// mouse scroll
let onMouseMove = (e: MouseEvent) => {
let rect = this.thumb.getBoundingClientRect();
@ -194,101 +252,104 @@ export default class Scrollable { @@ -194,101 +252,104 @@ export default class Scrollable {
this.container.addEventListener('scroll', this.onScroll.bind(this));
//this.container.append(this.paddingTopDiv);
Array.from(el.children).forEach(c => this.container.append(c));
//this.container.append(this.paddingBottomDiv);
el.append(this.container);//container.append(el);
el.append(this.container);
this.container.parentElement.append(this.thumb);
this.resize();
}
public resize() {
console.time('scroll resize');
// @ts-ignore
this.scrollSize = this.container[this.scrollType];
let rect = this.container.getBoundingClientRect();
// @ts-ignore
this.size = rect[this.type];
public async resize() {
//console.time('scroll resize');
await fastdom.measure(() => {
// @ts-ignore
this.scrollSize = this.container[this.scrollType];
let rect = this.container.getBoundingClientRect();
// @ts-ignore
this.size = rect[this.type];
});
if(!this.size || this.size == this.scrollSize) {
this.thumbSize = 0;
await fastdom.mutate(() => {
if(!this.size || this.size == this.scrollSize) {
this.thumbSize = 0;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
//console.timeEnd('scroll resize');
return;
}
//if(!height) return;
let divider = this.scrollSize / this.size / 0.5;
this.thumbSize = this.size / divider;
if(this.thumbSize < 20) this.thumbSize = 20;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
console.timeEnd('scroll resize');
return;
}
//if(!height) return;
let divider = this.scrollSize / this.size / 0.5;
this.thumbSize = this.size / divider;
if(this.thumbSize < 20) this.thumbSize = 20;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
});
console.timeEnd('scroll resize');
//console.timeEnd('scroll resize');
// @ts-ignore
//console.log('onresize', thumb.style[type], thumbHeight, height);
}
public setVirtualContainer(el?: HTMLElement) {
public async setVirtualContainer(el?: HTMLElement) {
this.splitUp = el;
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
this.paddings.up = this.paddings.down = 0;
if(this.paddingTopDiv.parentElement) {
this.paddingTopDiv.style.height = '';
this.paddingBottomDiv.style.height = '';
fastdom.mutate(() => {
this.paddingTopDiv.style.height = '';
this.paddingBottomDiv.style.height = '';
});
}
/* this.topObserver.unobserve(this.paddingTopDiv);
this.bottomObserver.unobserve(this.paddingBottomDiv);
this.topObserver.observe(this.paddingTopDiv);
this.bottomObserver.observe(this.paddingBottomDiv); */
if(el) {
el.parentElement.insertBefore(this.paddingTopDiv, el);
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
fastdom.mutate(() => {
el.parentElement.insertBefore(this.paddingTopDiv, el);
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
});
}
}
public onScroll() {
// @ts-ignore
//let st = container[scrollSide];
console.time('scroll onScroll');
public async onScroll() {
//console.time('scroll onScroll');
let {value, maxValue} = await fastdom.measure(() => {
// @ts-ignore
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
this.resize();
}
// @ts-ignore
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
this.resize();
}
// @ts-ignore
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
let maxValue = 100 - (this.thumbSize / this.size * 100);
return {value, maxValue};
});
// @ts-ignore
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
let maxValue = 100 - (this.thumbSize / this.size * 100);
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
// @ts-ignore
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
fastdom.mutate(() => {
// @ts-ignore
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
});
console.timeEnd('scroll onScroll');
//console.timeEnd('scroll onScroll');
}
public onTopIntersection(entry: IntersectionObserverEntry) {
// console.log('onTopIntersection');
public async onTopIntersection(entry: IntersectionObserverEntry) {
console.log('onTopIntersection');
if(this.hiddenElements.up.length && this.paddings.up) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
let fragment = document.createDocumentFragment();
while(needHeight > 0 && this.paddings.up) {
let child = this.hiddenElements.up.pop();
@ -300,29 +361,31 @@ export default class Scrollable { @@ -300,29 +361,31 @@ export default class Scrollable {
break;
}
/* await new Promise((resolve, reject) => {
window.requestAnimationFrame(resolve);
}); */
this.splitUp.prepend(child.element);
let height = child.height || child.element.scrollHeight;
fragment.prepend(child.element);
let height = child.height;
needHeight -= height;
this.paddings.up -= height;
}
await fastdom.mutate(() => {
this.splitUp.prepend(fragment);
this.paddingTopDiv.style.height = this.paddings.up + 'px';
}
});
} else {
this.paddingTopDiv.style.height = '0px';
await fastdom.mutate(() => {
this.paddingTopDiv.style.height = '0px';
});
}
}
public onBottomIntersection(entry: IntersectionObserverEntry) {
// console.log('onBottomIntersection');
public async onBottomIntersection(entry: IntersectionObserverEntry) {
console.log('onBottomIntersection');
if(this.hiddenElements.down.length && this.paddings.down) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
let needHeight = entry.intersectionRect.height || entry.boundingClientRect.height;
let fragment = document.createDocumentFragment();
while(needHeight > 0 && this.paddings.down) {
let child = this.hiddenElements.down.shift();
@ -332,18 +395,22 @@ export default class Scrollable { @@ -332,18 +395,22 @@ export default class Scrollable {
break;
}
this.splitUp.append(child.element);
let height = child.height || child.element.scrollHeight;
fragment.appendChild(child.element);
let height = child.height;
needHeight -= height;
this.paddings.down -= height;
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
}
await fastdom.mutate(() => {
this.splitUp.appendChild(fragment);
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
});
if(this.onAddedBottom) this.onAddedBottom();
} else {
this.paddingBottomDiv.style.height = '0px';
await fastdom.mutate(() => {
this.paddingBottomDiv.style.height = '0px';
});
}
}
@ -351,16 +418,45 @@ export default class Scrollable { @@ -351,16 +418,45 @@ export default class Scrollable {
}
public splitAppend(...smth: (string | Node)[]) {
this.splitUp.append(...smth);
public prepend(...smth: (string | Node)[]) {
if(this.splitUp) {
this.splitUp.prepend(...smth);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.unobserve(node as Element);
this.splitObserver.observe(node as Element);
}
}
} else {
this.container.prepend(...smth);
}
}
public append(...smth: (string | Node)[]) {
if(this.splitUp) {
this.splitUp.append(...smth);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.observe(node as Element);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.unobserve(node as Element);
this.splitObserver.observe(node as Element);
}
}
} else {
this.container.append(...smth);
}
}
public insertBefore(newChild: Element, refChild: Element) {
if(this.splitUp) {
this.splitObserver.unobserve(newChild);
return this.splitUp.insertBefore(newChild, refChild);
}
return this.container.insertBefore(newChild, refChild);
}
set scrollTop(y: number) {
this.container.scrollTop = y;
}

428
src/components/scrollable_good.ts

@ -0,0 +1,428 @@ @@ -0,0 +1,428 @@
import { cancelEvent } from "../lib/utils";
//import {measure} from 'fastdom/fastdom.min';
import {measure} from 'fastdom';
export default class Scrollable {
public container: HTMLDivElement;
public thumb: HTMLDivElement;
public type: string;
public side: string;
public scrollType: string;
public scrollSide: string;
public clientAxis: string;
public scrollSize = -1;
public size = 0;
public thumbSize = 0;
public hiddenElements: {
up: {element: Element, height: number}[],
down: {element: Element, height: number}[]
} = {
up: [],
down: []
};
public paddings = {up: 0, down: 0};
public paddingTopDiv: HTMLDivElement;
public paddingBottomDiv: HTMLDivElement;
public splitUp: HTMLElement;
public splitOffset = 1500;
public onAddedBottom: () => void = null;
public topObserver: IntersectionObserver;
public isTopIntersecting: boolean;
public bottomObserver: IntersectionObserver;
public isBottomIntersecting: boolean;
public splitObserver: IntersectionObserver;
constructor(public el: HTMLDivElement, x = false, y = true) {
this.container = document.createElement('div');
this.container.classList.add('scrollable');
let arr = [];
for(let i = 0.001; i < 1; i += 0.001) arr.push(i);
this.topObserver = new IntersectionObserver(entries => {
let entry = entries[0];
// console.log('top intersection:', entries, this.isTopIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isTopIntersecting = entry.isIntersecting) {
this.onTopIntersection(entry);
}
// console.log('top intersection end');
}, {threshold: arr});
this.bottomObserver = new IntersectionObserver(entries => {
let entry = entries[0];
// console.log('bottom intersection:', entries, this.isBottomIntersecting, entry.isIntersecting, entry.intersectionRatio > 0);
if(this.isBottomIntersecting = entry.isIntersecting) {
this.onBottomIntersection(entry);
if(this.onScrolledBottom) this.onScrolledBottom();
}
}, {threshold: arr});
this.splitObserver = new IntersectionObserver(entries => {
//console.log('splitObserver', entries);
for(let entry of entries) {
//console.log('onscroll entry', entry.target, entry.isIntersecting, entry);
if(!entry.isIntersecting && entry.target.parentElement && entry.rootBounds) {
let child = entry.target;
//console.log('onscroll entry', entry.boundingClientRect, child, entry);
let isTop = entry.boundingClientRect.top <= 0;
let isBottom = entry.rootBounds.height <= entry.boundingClientRect.top;
let needHeight = this.splitOffset;
if(isTop) {
let sliced: Element[] = [/* child */];
do {
if(needHeight > 0) {
needHeight -= child.scrollHeight;
} else {
sliced.push(child);
}
} while(child = child.previousElementSibling);
let length = sliced.length;
for(let i = length - 1; i >= 0; --i) {
let child = sliced[i];
let height = child.scrollHeight;
this.paddings.up += height;
this.hiddenElements.up.push({element: child, height});
child.parentElement.removeChild(child);
}
this.paddingTopDiv.style.height = this.paddings.up + 'px';
//console.log('onscroll sliced up', sliced);
} else if(isBottom) {
let sliced: Element[] = [/* child */];
do {
if(needHeight > 0) {
needHeight -= child.scrollHeight;
} else {
sliced.push(child);
}
} while(child = child.nextElementSibling);
let length = sliced.length;
for(let i = length - 1; i >= 0; --i) {
let child = sliced[i];
let height = child.scrollHeight;
this.paddings.down += height;
this.hiddenElements.down.unshift({element: child, height});
child.parentElement.removeChild(child);
}
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
//console.log('onscroll sliced down', sliced);
}
//console.log('splitObserver', entry, entry.target, isTop);
}
}
});
if(x) {
this.container.classList.add('scrollable-x');
this.type = 'width';
this.side = 'left';
this.scrollType = 'scrollWidth';
this.scrollSide = 'scrollLeft';
this.clientAxis = 'clientX';
let scrollHorizontally = (e: any) => {
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
this.container.scrollLeft -= (delta * 20);
e.preventDefault();
};
if(this.container.addEventListener) {
// IE9, Chrome, Safari, Opera
this.container.addEventListener("mousewheel", scrollHorizontally, false);
// Firefox
this.container.addEventListener("DOMMouseScroll", scrollHorizontally, false);
} else {
// IE 6/7/8
// @ts-ignore
this.container.attachEvent("onmousewheel", scrollHorizontally);
}
} else if(y) {
this.container.classList.add('scrollable-y');
this.type = 'height';
this.side = 'top';
this.scrollType = 'scrollHeight';
this.scrollSide = 'scrollTop';
this.clientAxis = 'clientY';
} else {
throw new Error('no side for scroll');
}
this.thumb = document.createElement('div');
this.thumb.className = 'scrollbar-thumb';
// @ts-ignore
this.thumb.style[this.type] = '30px';
let onMouseMove = (e: MouseEvent) => {
let rect = this.thumb.getBoundingClientRect();
let diff: number;
// @ts-ignore
diff = e[this.clientAxis] - rect[this.side];
// @ts-ignore
this.container[this.scrollSide] += diff * 0.5;
// console.log('onMouseMove', e, diff);
cancelEvent(e);
};
this.thumb.addEventListener('mousedown', () => {
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', () => {
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
});
//this.container.addEventListener('mouseover', this.resize.bind(this)); // omg
window.addEventListener('resize', this.resize.bind(this));
this.paddingTopDiv = document.createElement('div');
this.paddingTopDiv.classList.add('scroll-padding');
this.paddingBottomDiv = document.createElement('div');
this.paddingBottomDiv.classList.add('scroll-padding');
this.topObserver.observe(this.paddingTopDiv);
this.bottomObserver.observe(this.paddingBottomDiv);
this.container.addEventListener('scroll', this.onScroll.bind(this));
//this.container.append(this.paddingTopDiv);
Array.from(el.children).forEach(c => this.container.append(c));
//this.container.append(this.paddingBottomDiv);
el.append(this.container);//container.append(el);
this.container.parentElement.append(this.thumb);
this.resize();
}
public resize() {
//console.time('scroll resize');
// @ts-ignore
this.scrollSize = this.container[this.scrollType];
let rect = this.container.getBoundingClientRect();
// @ts-ignore
this.size = rect[this.type];
if(!this.size || this.size == this.scrollSize) {
this.thumbSize = 0;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
//console.timeEnd('scroll resize');
return;
}
//if(!height) return;
let divider = this.scrollSize / this.size / 0.5;
this.thumbSize = this.size / divider;
if(this.thumbSize < 20) this.thumbSize = 20;
// @ts-ignore
this.thumb.style[this.type] = this.thumbSize + 'px';
//console.timeEnd('scroll resize');
// @ts-ignore
//console.log('onresize', thumb.style[type], thumbHeight, height);
}
public setVirtualContainer(el?: HTMLElement) {
this.splitUp = el;
this.hiddenElements.up.length = this.hiddenElements.down.length = 0;
this.paddings.up = this.paddings.down = 0;
if(this.paddingTopDiv.parentElement) {
this.paddingTopDiv.style.height = '';
this.paddingBottomDiv.style.height = '';
}
/* this.topObserver.unobserve(this.paddingTopDiv);
this.bottomObserver.unobserve(this.paddingBottomDiv);
this.topObserver.observe(this.paddingTopDiv);
this.bottomObserver.observe(this.paddingBottomDiv); */
if(el) {
el.parentElement.insertBefore(this.paddingTopDiv, el);
el.parentNode.insertBefore(this.paddingBottomDiv, el.nextSibling);
}
}
public onScroll() {
// @ts-ignore
//let st = container[scrollSide];
//console.time('scroll onScroll');
// @ts-ignore
if(this.container[this.scrollType] != this.scrollSize || this.thumbSize == 0) {
this.resize();
}
// @ts-ignore
let value = this.container[this.scrollSide] / (this.scrollSize - this.size) * 100;
let maxValue = 100 - (this.thumbSize / this.size * 100);
//console.log('onscroll', container.scrollHeight, thumbHeight, height, value, maxValue);
// @ts-ignore
this.thumb.style[this.side] = (value >= maxValue ? maxValue : value) + '%';
//console.timeEnd('scroll onScroll');
}
public onTopIntersection(entry: IntersectionObserverEntry) {
// console.log('onTopIntersection');
if(this.hiddenElements.up.length && this.paddings.up) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
while(needHeight > 0 && this.paddings.up) {
let child = this.hiddenElements.up.pop();
// console.log('top returning from hidden', child);
if(!child) {
this.paddings.up = 0;
this.paddingTopDiv.style.height = '0px';
break;
}
/* await new Promise((resolve, reject) => {
window.requestAnimationFrame(resolve);
}); */
this.splitUp.prepend(child.element);
let height = child.height || child.element.scrollHeight;
needHeight -= height;
this.paddings.up -= height;
this.paddingTopDiv.style.height = this.paddings.up + 'px';
}
} else {
this.paddingTopDiv.style.height = '0px';
}
}
public onBottomIntersection(entry: IntersectionObserverEntry) {
// console.log('onBottomIntersection');
if(this.hiddenElements.down.length && this.paddings.down) {
let needHeight = entry.intersectionRect.height + this.splitOffset;
while(needHeight > 0 && this.paddings.down) {
let child = this.hiddenElements.down.shift();
if(!child) {
this.paddings.down = 0;
this.paddingBottomDiv.style.height = '0px';
break;
}
this.splitUp.append(child.element);
let height = child.height || child.element.scrollHeight;
needHeight -= height;
this.paddings.down -= height;
this.paddingBottomDiv.style.height = this.paddings.down + 'px';
}
if(this.onAddedBottom) this.onAddedBottom();
} else {
this.paddingBottomDiv.style.height = '0px';
}
}
public onScrolledBottom() {
}
public prepend(...smth: (string | Node)[]) {
if(this.splitUp) {
this.splitUp.prepend(...smth);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.unobserve(node as Element);
this.splitObserver.observe(node as Element);
}
}
} else {
this.container.prepend(...smth);
}
}
public append(...smth: (string | Node)[]) {
if(this.splitUp) {
this.splitUp.append(...smth);
for(let node of smth) {
if(typeof(node) !== 'string') {
this.splitObserver.unobserve(node as Element);
this.splitObserver.observe(node as Element);
}
}
} else {
this.container.append(...smth);
}
}
public insertBefore(newChild: Element, refChild: Element) {
if(this.splitUp) {
this.splitObserver.unobserve(newChild);
return this.splitUp.insertBefore(newChild, refChild);
}
return this.container.insertBefore(newChild, refChild);
}
set scrollTop(y: number) {
this.container.scrollTop = y;
}
get scrollTop() {
return this.container.scrollTop;
}
get scrollHeight() {
return this.container.scrollHeight;
}
get parentElement() {
return this.container.parentElement;
}
get offsetHeight() {
return this.container.offsetHeight;
}
}

9
src/components/wrappers.ts

@ -329,7 +329,7 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -329,7 +329,7 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
let lastIndex = 0;
interval = setInterval(() => {
if(lastIndex >= svg.childElementCount) {
if(lastIndex > svg.childElementCount || isNaN(audio.duration)) {
clearInterval(interval);
return;
}
@ -337,11 +337,14 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -337,11 +337,14 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
// @ts-ignore
timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true);
lastIndex = Math.round(audio.currentTime / audio.duration * 62);
//svg.children[lastIndex].setAttributeNS(null, 'fill', '#000');
svg.children[lastIndex].classList.add('active');
++lastIndex;
//++lastIndex;
//console.log('lastIndex:', lastIndex, audio.currentTime);
}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */);
//}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */);
}, 20);
} else {
audio.pause();
toggle.classList.add('tgico-largeplay');

22
src/lib/appManagers/appDialogsManager.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import apiManager from "../mtproto/apiManager";
import apiFileManager from '../mtproto/apiFileManager';
import { $rootScope, findUpTag, isElementInViewport, langPack } from "../utils";
import { $rootScope, findUpTag, langPack } from "../utils";
import appImManager from "./appImManager";
import appPeersManager from './appPeersManager';
import appMessagesManager from "./appMessagesManager";
@ -8,6 +8,7 @@ import appUsersManager from "./appUsersManager"; @@ -8,6 +8,7 @@ import appUsersManager from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor";
import { ripple } from "../../components/misc";
import appSidebarLeft from "./appSidebarLeft";
import Scrollable from "../../components/scrollable";
type DialogDom = {
avatarDiv: HTMLDivElement,
@ -25,8 +26,8 @@ export class AppDialogsManager { @@ -25,8 +26,8 @@ export class AppDialogsManager {
public chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement;
public pinnedDelimiter: HTMLDivElement;
public chatsHidden: any;
public chatsArchivedHidden: any;
public chatsHidden: Scrollable["hiddenElements"];
public chatsArchivedHidden: Scrollable["hiddenElements"];
public myID = 0;
public doms: {[peerID: number]: DialogDom} = {};
@ -153,7 +154,7 @@ export class AppDialogsManager { @@ -153,7 +154,7 @@ export class AppDialogsManager {
}
public sortDom(archived = false) {
return;
// return;
let dialogs = appMessagesManager.dialogsStorage.dialogs.slice();
@ -205,6 +206,9 @@ export class AppDialogsManager { @@ -205,6 +206,9 @@ export class AppDialogsManager {
let hiddenLength: number = chatsHidden.up.length;
let inViewportLength = chatList.childElementCount;
let hiddenConcated = chatsHidden.up.concat(chatsHidden.down);
//console.log('sortDom clearing innerHTML', archived, hiddenLength, inViewportLength);
chatList.innerHTML = '';
@ -214,16 +218,18 @@ export class AppDialogsManager { @@ -214,16 +218,18 @@ export class AppDialogsManager {
if(!dom) return;
if(inUpper.length < hiddenLength) {
inUpper.push({element: dom.listEl, height: 0});
let child = hiddenConcated.find(obj => obj.element == dom.listEl);
inUpper.push({element: dom.listEl, height: child ? child.height : 0});
} else if(inViewportIndex <= inViewportLength - 1) {
chatList.append(dom.listEl);
++inViewportIndex;
} else {
inBottom.push({element: dom.listEl, height: 0});
let child = hiddenConcated.find(obj => obj.element == dom.listEl);
inBottom.push({element: dom.listEl, height: child ? child.height : 0});
}
});
//////console.log('sortDom', sorted.length, inUpper.length, chatList.childElementCount, inBottom.length);
//console.log('sortDom', sorted.length, inUpper.length, chatList.childElementCount, inBottom.length);
chatsHidden.up = inUpper;
chatsHidden.down = inBottom;
@ -524,7 +530,7 @@ export class AppDialogsManager { @@ -524,7 +530,7 @@ export class AppDialogsManager {
this.domsArchived[dialog.peerID] = dom;
} else {
//this.chatList.append(li);
appSidebarLeft.scroll.splitAppend(li);
appSidebarLeft.scroll.append(li);
this.doms[dialog.peerID] = dom;
}

30
src/lib/appManagers/appImManager.ts

@ -70,8 +70,6 @@ export class AppImManager { @@ -70,8 +70,6 @@ export class AppImManager {
public chatInner = document.getElementById('bubbles-inner') as HTMLDivElement;
public searchBtn = this.pageEl.querySelector('.chat-search-button') as HTMLButtonElement;
public goDownBtn = this.pageEl.querySelector('#bubbles-go-down') as HTMLButtonElement;
public firstContainerDiv: HTMLDivElement;
public lastContainerDiv: HTMLDivElement;
private getHistoryPromise: Promise<boolean>;
private getHistoryTimeout = 0;
@ -185,7 +183,7 @@ export class AppImManager { @@ -185,7 +183,7 @@ export class AppImManager {
this.renderMessagesByIDs(msgIDs);
appDialogsManager.sortDom();
//appDialogsManager.sortDom();
});
$rootScope.$on('history_delete', (e: CustomEvent) => {
@ -627,6 +625,8 @@ export class AppImManager { @@ -627,6 +625,8 @@ export class AppImManager {
}
public deleteEmptySideDivs() {
return;
let nodes = Array.from(this.chatInner.childNodes) as HTMLDivElement[];
nodes.filter((node) => {
let childElementCount = node.childElementCount;
@ -844,10 +844,10 @@ export class AppImManager { @@ -844,10 +844,10 @@ export class AppImManager {
}
public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer);
this.scrollable = new Scrollable(this.bubblesContainer, false, true, 1500);
this.scroll = this.scrollable.container;
//this.scrollable.setVirtualContainer(this.chatInner);
this.scrollable.setVirtualContainer(this.chatInner);
this.scrollPosition = new ScrollPosition(this.chatInner);
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
@ -949,11 +949,6 @@ export class AppImManager { @@ -949,11 +949,6 @@ export class AppImManager {
this.scrolledAllDown = false;
this.muted = false;
if(this.lastContainerDiv) this.lastContainerDiv.remove();
if(this.firstContainerDiv) this.firstContainerDiv.remove();
this.lastContainerDiv = undefined;
this.firstContainerDiv = undefined;
for(let i in this.bubbles) {
let bubble = this.bubbles[i];
bubble.remove();
@ -975,6 +970,8 @@ export class AppImManager { @@ -975,6 +970,8 @@ export class AppImManager {
// clear messages
this.chatInner.innerHTML = '';
this.scrollable.setVirtualContainer(this.chatInner);
//appSidebarRight.minMediaID = {};
}
@ -1635,10 +1632,15 @@ export class AppImManager { @@ -1635,10 +1632,15 @@ export class AppImManager {
if(updatePosition) {
bubble.classList.add(our ? 'is-out' : 'is-in');
if(reverse) {
/* if(reverse) {
this.chatInner.prepend(bubble);
} else {
this.chatInner.append(bubble);
} */
if(reverse) {
this.scrollable.prepend(bubble);
} else {
this.scrollable.append(bubble);
}
let justDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
@ -1673,12 +1675,12 @@ export class AppImManager { @@ -1673,12 +1675,12 @@ export class AppImManager {
//this.chatInner.insertBefore(div, containerDiv);
//containerDiv.insertBefore(div, bubble);
this.chatInner.insertBefore(div, bubble);
this.scrollable.insertBefore(div, bubble);// this.chatInner.insertBefore(div, bubble);
} else {
let dateMessage = this.dateMessages[dateTimestamp];
if(dateMessage.firstTimestamp > date.getTime()) {
//this.chatInner.insertBefore(dateMessage.div, containerDiv);
this.chatInner.insertBefore(dateMessage.div, bubble);
this.scrollable.insertBefore(dateMessage.div, bubble);// this.chatInner.insertBefore(dateMessage.div, bubble);
}
}
}
@ -1707,7 +1709,7 @@ export class AppImManager { @@ -1707,7 +1709,7 @@ export class AppImManager {
let loadCount = Object.keys(this.bubbles).length > 0 ?
20 :
(this.chatInner.parentElement.parentElement.scrollHeight) / 30 * 1.25 | 0;
this.scrollable.container.parentElement.scrollHeight / 30 * 1.25 | 0;
/* if(testScroll) {
loadCount = 1;

23
src/lib/appManagers/appPhotosManager.ts

@ -7,6 +7,9 @@ import apiFileManager from "../mtproto/apiFileManager"; @@ -7,6 +7,9 @@ import apiFileManager from "../mtproto/apiFileManager";
import apiManager from "../mtproto/apiManager";
//import { MTPhotoSize } from "../../components/misc";
//import fastdom from "fastdom";
//import 'fastdom/fastdom-strict'; // exclude in production
type MTPhoto = {
_: 'photo',
pFlags: any,
@ -24,22 +27,26 @@ export class AppPhotosManager { @@ -24,22 +27,26 @@ export class AppPhotosManager {
private photos: {
[id: string]: MTPhoto
} = {};
public windowW = document.body.scrollWidth;
public windowH = document.body.scrollHeight;
public windowW = 0;
public windowH = 0;
public static jf = new Uint8Array(bytesFromHex('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00'));
public static Df = bytesFromHex('ffd9');
constructor() {
window.addEventListener('resize', (e) => {
this.windowW = document.body.scrollWidth;
this.windowH = document.body.scrollHeight;
//fastdom.measure(() => {
this.windowW = document.body.scrollWidth;
this.windowH = document.body.scrollHeight;
//});
//console.log(`Set windowW, windowH: ${this.windowW}x${this.windowH}`);
});
/* $rootScope.openPhoto = openPhoto
$rootScope.preloadPhoto = preloadPhoto; */
//fastdom.measure(() => {
console.log('measure works');
this.windowW = document.body.scrollWidth;
this.windowH = document.body.scrollHeight;
//});
}
public savePhoto(apiPhoto: any, context?: any) {

29
src/lib/appManagers/appSidebarLeft.ts

@ -3,7 +3,7 @@ import { putPreloader, formatPhoneNumber } from "../../components/misc"; @@ -3,7 +3,7 @@ import { putPreloader, formatPhoneNumber } from "../../components/misc";
import Scrollable from '../../components/scrollable';
import appMessagesManager, { AppMessagesManager } from "./appMessagesManager";
import appDialogsManager from "./appDialogsManager";
import { isElementInViewport, numberWithCommas, cancelEvent } from "../utils";
import { isElementInViewport, numberWithCommas } from "../utils";
import appMessagesIDsManager from "./appMessagesIDsManager";
import appImManager from "./appImManager";
import appUsersManager from "./appUsersManager";
@ -25,7 +25,7 @@ class SearchGroup { @@ -25,7 +25,7 @@ class SearchGroup {
this.container.append(this.nameEl, this.list);
this.container.style.display = 'none';
appDialogsManager.setListClickListener(this.list);
//appDialogsManager.setListClickListener(this.list);
}
clear() {
@ -88,7 +88,7 @@ class AppSidebarLeft { @@ -88,7 +88,7 @@ class AppSidebarLeft {
this.chatsPreloader = document.createElement('div');
this.chatsPreloader.classList.add('preloader');
putPreloader(this.chatsPreloader);
this.chatsContainer.append(this.chatsPreloader);
//this.chatsContainer.append(this.chatsPreloader);
this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5);
@ -128,20 +128,16 @@ class AppSidebarLeft { @@ -128,20 +128,16 @@ class AppSidebarLeft {
//this.toolsBtn.classList.add('tgico-back');
});
/* this.listsContainer.insertBefore(this.searchMessagesList, this.listsContainer.lastElementChild);
for(let i = 0; i < 25; ++i) {
/* for(let i = 0; i < 100; ++i) {
let li = document.createElement('li');
li.innerHTML = `<div class="user-avatar is-online" style="font-size: 0px;"><img src="assets/img/camomile.jpg"></div><div class="user-caption"><p><span class="user-title">Влад</span><span><span class="message-status"></span><span class="message-time">14:41</span></span></p><p><span class="user-last-message">это важно</span><span class="tgico-pinnedchat"></span></p></div><div class="c-ripple"><span class="c-ripple__circle" style="top: 65px; left: 338.5px;"></span></div>`;
this.searchMessagesList.append(li);
li.dataset.id = '' + i;
li.innerHTML = `<div class="rp"><div class="user-avatar" style="background-color: rgb(166, 149, 231); font-size: 0px;"><img src="blob:https://localhost:9000/ce99a2a3-f34b-4ca1-a09e-f716f89930d8"></div><div class="user-caption"><p><span class="user-title">${i}</span><span><span class="message-status"></span><span class="message-time">18:33</span></span></p><p><span class="user-last-message"><b>Ильяс: </b>Гагагагга</span><span></span></p></div></div>`;
this.scroll.append(li);
} */
this.listsContainer.addEventListener('scroll', this.onSidebarScroll.bind(this));
//this.searchContainer.append(this.listsContainer);
this.searchInput.addEventListener('focus', (e) => {
/* this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle');
this.toolsBtn.classList.add('tgico-back'); */
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.searchContainer.classList.add('active');
@ -154,17 +150,10 @@ class AppSidebarLeft { @@ -154,17 +150,10 @@ class AppSidebarLeft {
this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
/* this.toolsBtn.classList.add('tgico-menu');
this.toolsBtn.classList.remove('tgico-back'); */
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
this.backBtn.click();
/* setTimeout(() => {
//this.toolsBtn.click();
this.toolsBtn.classList.add('btn-menu-toggle');
}, 200); */
}
/* this.peerID = 0;
@ -223,6 +212,8 @@ class AppSidebarLeft { @@ -223,6 +212,8 @@ class AppSidebarLeft {
}
public async loadDialogs(archived = false) {
//return;
if(this.loadDialogsPromise/* || 1 == 1 */) return this.loadDialogsPromise;
(archived ? this.chatsArchivedContainer : this.chatsContainer).append(this.chatsPreloader);

4
src/lib/services.ts

@ -32,7 +32,7 @@ export const appDocsManager = AppDocsManager; @@ -32,7 +32,7 @@ export const appDocsManager = AppDocsManager;
export const appSidebarRight = AppSidebarRight;
export const appSidebarLeft = AppSidebarLeft;
/* (window as any).Services = {
(window as any).Services = {
appUsersManager,
appChatsManager,
apiUpdatesManager,
@ -48,4 +48,4 @@ export const appSidebarLeft = AppSidebarLeft; @@ -48,4 +48,4 @@ export const appSidebarLeft = AppSidebarLeft;
appSidebarRight,
appSidebarLeft
//appSharedMediaManager
}; */
};

4
src/scss/partials/_chatlist.scss

@ -70,9 +70,9 @@ @@ -70,9 +70,9 @@
margin: 0 8.5px 0 8px;
overflow: hidden;
/* &:hover {
&:hover {
background: rgba(112, 117, 121, .08);
} */
}
}
li.active > .rp {

Loading…
Cancel
Save