Global search 'saved messages'

Fix blinking down arrown
Lazy load queue fixes
GIFs for Chrome
This commit is contained in:
morethanwords 2020-09-21 20:34:19 +03:00
parent 8b26d22cc4
commit ae193a10db
16 changed files with 403 additions and 232 deletions

View File

@ -100,6 +100,12 @@ export class AnimationIntersector {
if((destroy || (!isInDOM(el) && !this.lockedGroups[group]))/* && false */) {
//console.log('destroy animation');
animation.remove();
if(animation instanceof HTMLVideoElement) {
animation.src = '';
animation.load();
}
for(const group in this.byGroups) {
this.byGroups[group].findAndSplice(p => p == player);
}

View File

@ -4,10 +4,11 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import appPeersManager from '../lib/appManagers/appPeersManager';
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { escapeRegExp } from "../lib/utils";
import { $rootScope, escapeRegExp } from "../lib/utils";
import { formatPhoneNumber } from "./misc";
import appChatsManager from "../lib/appManagers/appChatsManager";
import SearchInput from "./searchInput";
import { Peer } from "../layer";
export class SearchGroup {
container: HTMLDivElement;
@ -48,6 +49,11 @@ export class SearchGroup {
}
}
/**
* * Saved будет использована только для вывода одного элемента - избранное
*/
type SearchGroupType = 'saved' | 'contacts' | 'globalContacts' | 'messages' | string;
export default class AppSearch {
private minMsgID = 0;
private loadedCount = -1;
@ -66,15 +72,15 @@ export default class AppSearch {
private scrollable: Scrollable;
constructor(public container: HTMLElement, public searchInput: SearchInput, public searchGroups: {[group: string]: SearchGroup}, public onSearch?: (count: number) => void) {
constructor(public container: HTMLElement, public searchInput: SearchInput, public searchGroups: {[group in SearchGroupType]: SearchGroup}, public onSearch?: (count: number) => void) {
this.scrollable = new Scrollable(this.container);
this.listsContainer = this.scrollable.container as HTMLDivElement;
for(let i in this.searchGroups) {
this.listsContainer.append(this.searchGroups[i].container);
this.listsContainer.append(this.searchGroups[i as SearchGroupType].container);
}
if(this.searchGroups['messages']) {
this.scrollable.setVirtualContainer(this.searchGroups['messages'].list);
if(this.searchGroups.messages) {
this.scrollable.setVirtualContainer(this.searchGroups.messages.list);
}
this.searchInput.onChange = (value) => {
@ -92,7 +98,7 @@ export default class AppSearch {
if(!this.query.trim()) return;
if(!this.searchTimeout) {
this.searchTimeout = setTimeout(() => {
this.searchTimeout = window.setTimeout(() => {
this.searchMore();
this.searchTimeout = 0;
}, 0);
@ -114,7 +120,7 @@ export default class AppSearch {
this.loadedContacts = false;
for(let i in this.searchGroups) {
this.searchGroups[i].clear();
this.searchGroups[i as SearchGroupType].clear();
}
this.searchPromise = null;
@ -127,6 +133,13 @@ export default class AppSearch {
this.searchInput.input.focus();
}
private renderSaved() {
const group = this.searchGroups.contacts;
let {dialog, dom} = appDialogsManager.addDialog($rootScope.myID, group.list, false);
dom.lastMessageSpan.innerHTML = 'chat with yourself';
group.setActive();
}
public searchMore() {
if(this.searchPromise) return this.searchPromise;
@ -145,18 +158,40 @@ export default class AppSearch {
const maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0] || 0;
if(!this.peerID && !maxID && !this.loadedContacts) {
appUsersManager.searchContacts(query, 20).then((contacts: any) => {
let renderedSaved = false;
if('saved messages'.includes(query.toLowerCase())
|| appUsersManager.getUser($rootScope.myID).sortName.includes(query.toLowerCase())/* && this.searchGroups.hasOwnProperty('saved') */) {
this.renderSaved();
renderedSaved = true;
}
appUsersManager.searchContacts(query, 20).then((contacts) => {
if(this.searchInput.value != query) {
return;
}
this.loadedContacts = true;
///////this.log('input search contacts result:', contacts);
// set saved message as first peer to render
const peer = contacts.my_results.findAndSplice(p => (p as Peer.peerUser).user_id == $rootScope.myID);
if(peer) {
contacts.my_results.unshift(peer);
}
let setResults = (results: any, group: SearchGroup, showMembersCount = false) => {
results.forEach((inputPeer: any) => {
//console.log('input search contacts result:', contacts);
let setResults = (results: Peer[], group: SearchGroup, showMembersCount = false) => {
results.forEach((inputPeer) => {
let peerID = appPeersManager.getPeerID(inputPeer);
if(peerID == $rootScope.myID) {
if(!renderedSaved) {
this.renderSaved();
}
return;
}
let peer = appPeersManager.getPeer(peerID);
let originalDialog = appMessagesManager.getDialogByPeerID(peerID)[0];
@ -194,7 +229,11 @@ export default class AppSearch {
});
if(results.length) group.setActive();
else group.clear();
else if(renderedSaved) { // удалить все пункты снизу
Array.from(group.list.children).slice(1).forEach(c => c.remove());
} else {
group.clear();
}
};
setResults(contacts.my_results, this.searchGroups.contacts, true);
@ -209,7 +248,7 @@ export default class AppSearch {
return;
}
console.log('input search result:', this.peerID, query, null, maxID, 20, res);
//console.log('input search result:', this.peerID, query, null, maxID, 20, res);
const {count, history, next_rate} = res;
@ -217,7 +256,7 @@ export default class AppSearch {
history.shift();
}
const searchGroup = this.searchGroups['messages'];
const searchGroup = this.searchGroups.messages;
searchGroup.setActive();
history.forEach((msgID: number) => {

View File

@ -13,8 +13,8 @@ export default class GifsTab implements EmoticonsTab {
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
const masonry = new GifsMasonry(gifsContainer);
const scroll = new Scrollable(this.content, 'y', 'GIFS', null);
const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll);
const preloader = putPreloader(this.content, true);
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((res) => {
@ -24,7 +24,7 @@ export default class GifsTab implements EmoticonsTab {
res.gifs.forEach((doc, idx) => {
res.gifs[idx] = doc = appDocsManager.saveDoc(doc);
//if(doc._ == 'documentEmpty') return;
masonry.add(doc as MyDocument, EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue);
//masonry.add(doc as MyDocument);
});
}

View File

@ -319,30 +319,6 @@ export default class StickersTab implements EmoticonsTab {
}
});
/* let closed = true;
emoticonsDropdown.events.onClose.push(() => {
closed = false;
this.lazyLoadQueue.lock();
});
emoticonsDropdown.events.onCloseAfter.push(() => {
const divs = this.lazyLoadQueue.intersector.getVisible();
for(const div of divs) {
this.processInvisibleDiv(div);
}
closed = true;
});
emoticonsDropdown.events.onOpenAfter.push(() => {
if(closed) {
this.lazyLoadQueue.unlockAndRefresh();
closed = false;
} else {
this.lazyLoadQueue.unlock();
}
}); */
emoticonsDropdown.events.onClose.push(() => {
this.lazyLoadQueue.lock();
});

View File

@ -1,19 +1,151 @@
import { calcImageInBox, findUpClassName } from "../lib/utils";
import { calcImageInBox } from "../lib/utils";
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc";
import LazyLoadQueue from "./lazyLoadQueue";
import { LazyLoadQueueRepeat2 } from "./lazyLoadQueue";
import { CancellablePromise, deferredPromise } from "../lib/polyfill";
import animationIntersector from "./animationIntersector";
import Scrollable from "./scrollable_new";
const width = 400;
const maxSingleWidth = width - 100;
const height = 100;
export default class GifsMasonry {
constructor(private element: HTMLElement) {
public lazyLoadQueue: LazyLoadQueueRepeat2;
private scrollPromise: CancellablePromise<void> = Promise.resolve();
constructor(private element: HTMLElement, private group: string, private scrollable: Scrollable) {
this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, (target, visible) => {
if(visible) {
this.processVisibleDiv(target);
} else {
this.processInvisibleDiv(target);
}
});
setInterval(() => {
// @ts-ignore
const players = animationIntersector.byGroups[group];
if(players) {
console.log(`GIFS RENDERED IN ${group}:`, players.length, players.filter(p => !p.animation.paused).length, this.lazyLoadQueue.intersector.getVisible().length);
}
}, .25e3);
let timeout = 0;
// memory leak
scrollable.container.addEventListener('scroll', () => {
if(timeout) {
clearTimeout(timeout);
} else {
this.scrollPromise = deferredPromise<void>();
//animationIntersector.checkAnimations(true, group);
}
timeout = window.setTimeout(() => {
timeout = 0;
this.scrollPromise.resolve();
//animationIntersector.checkAnimations(false, group);
}, 150);
});
}
public add(doc: MyDocument, group: string, lazyLoadQueue?: LazyLoadQueue) {
private processVisibleDiv = (div: HTMLElement) => {
const video = div.querySelector('video');
if(video) {
return;
}
const load = () => {
const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID);
const promise = this.scrollPromise.then(() => {
const promise = wrapVideo({
doc,
container: div as HTMLDivElement,
lazyLoadQueue: null,
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group: this.group,
noInfo: true,
});
promise.finally(() => {
const video = div.querySelector('video');
div.style.opacity = '';
const img = div.querySelector('img');
img && img.classList.add('hide');
if(video && !video.parentElement) {
setTimeout(() => {
video.src = '';
video.load();
const animations = animationIntersector.getAnimations(video);
animations.forEach(item => {
animationIntersector.checkAnimation(item, true, true);
});
}, 0);
}
//clearTimeout(timeout);
if(!this.lazyLoadQueue.intersector.isVisible(div)) {
this.processInvisibleDiv(div);
}
});
return promise;
});
/* let timeout = window.setTimeout(() => {
console.error('processVisibleDiv timeout', div, doc);
}, 1e3); */
return promise;
};
//return load();
this.lazyLoadQueue.push({div, load});
};
private processInvisibleDiv = async(div: HTMLElement) => {
return this.scrollPromise.then(async() => {
//return;
if(this.lazyLoadQueue.intersector.isVisible(div)) {
return;
}
const video = div.querySelector('video');
const img = div.querySelector('img');
if(img) {
img && img.classList.remove('hide');
await new Promise((resolve) => {
window.requestAnimationFrame(() => window.requestAnimationFrame(resolve));
});
}
if(this.lazyLoadQueue.intersector.isVisible(div)) {
return;
}
if(video) {
video.remove();
video.src = '';
video.load();
const animations = animationIntersector.getAnimations(video);
animations.forEach(item => {
animationIntersector.checkAnimation(item, true, true);
});
}
});
};
public add(doc: MyDocument) {
let gifWidth = doc.w;
let gifHeight = doc.h;
if(gifHeight < height) {
@ -21,8 +153,8 @@ export default class GifsMasonry {
gifHeight = height;
}
let willUseWidth = Math.min(maxSingleWidth, width, gifWidth);
let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
const willUseWidth = Math.min(maxSingleWidth, width, gifWidth);
const {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
/* wastedWidth += w;
@ -37,7 +169,7 @@ export default class GifsMasonry {
//console.log('gif:', gif, w, h);
let div = document.createElement('div');
const div = document.createElement('div');
div.classList.add('gif', 'fade-in-transition');
div.style.width = w + 'px';
div.style.opacity = '0';
@ -46,6 +178,9 @@ export default class GifsMasonry {
this.element.append(div);
//this.lazyLoadQueue.observe({div, load: this.processVisibleDiv});
this.lazyLoadQueue.observe(div);
//let preloader = new ProgressivePreloader(div);
const gotThumb = appDocsManager.getThumb(doc, false);
@ -62,72 +197,11 @@ export default class GifsMasonry {
}
}
let mouseOut = false;
const onMouseOver = (/* e: MouseEvent */) => {
//console.log('onMouseOver', doc.id);
//cancelEvent(e);
mouseOut = false;
wrapVideo({
doc,
container: div,
lazyLoadQueue,
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group,
noInfo: true,
});
const video = div.querySelector('video');
video.addEventListener('canplay', () => {
div.style.opacity = '';
if(!mouseOut) {
img && img.classList.add('hide');
} else {
img && img.classList.remove('hide');
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
}
}, {once: true});
};
const afterRender = () => {
if(img) {
div.append(img);
div.style.opacity = '';
}
if(lazyLoadQueue) {
onMouseOver();
} else {
div.addEventListener('mouseover', onMouseOver, {once: true});
div.addEventListener('mouseout', (e) => {
const toElement = (e as any).toElement as Element;
//console.log('onMouseOut', doc.id, e);
if(findUpClassName(toElement, 'gif') == div) {
return;
}
//cancelEvent(e);
mouseOut = true;
const cb = () => {
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
div.addEventListener('mouseover', onMouseOver, {once: true});
};
img && img.classList.remove('hide');
/* window.requestAnimationFrame(() => {
window.requestAnimationFrame();
}); */
if(img) window.requestAnimationFrame(() => window.requestAnimationFrame(cb));
else cb();
});
}
};
(gotThumb?.thumb?.url ? renderImageFromUrl(img, gotThumb.thumb.url, afterRender) : afterRender());

View File

@ -2,16 +2,19 @@ import { logger, LogLevels } from "../lib/logger";
import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersector";
type LazyLoadElementBase = {
div: HTMLDivElement,
load: (target?: HTMLDivElement) => Promise<any>
load: () => Promise<any>
};
type LazyLoadElement = LazyLoadElementBase & {
wasSeen?: boolean
type LazyLoadElement = Omit<LazyLoadElementBase, 'load'> & {
load: (target?: HTMLElement) => Promise<any>,
div: HTMLElement
wasSeen?: boolean,
};
const PARALLEL_LIMIT = 5;
export class LazyLoadQueueBase {
protected lazyLoadMedia: Array<LazyLoadElementBase> = [];
protected queue: Array<LazyLoadElementBase> = [];
protected inProcess: Set<LazyLoadElementBase> = new Set();
protected lockPromise: Promise<void> = null;
@ -19,13 +22,13 @@ export class LazyLoadQueueBase {
protected log = logger('LL', LogLevels.error);
constructor(protected parallelLimit = 5) {
constructor(protected parallelLimit = PARALLEL_LIMIT) {
}
public clear() {
this.inProcess.clear(); // ацтеки забьются, будет плохо
this.lazyLoadMedia.length = 0;
this.queue.length = 0;
// unreachable code
/* for(let item of this.inProcess) {
this.lazyLoadMedia.push(item);
@ -34,34 +37,40 @@ export class LazyLoadQueueBase {
public lock() {
if(this.lockPromise) return;
const perf = performance.now();
this.lockPromise = new Promise((resolve, reject) => {
this.unlockResolve = resolve;
});
this.lockPromise.then(() => {
this.log('was locked for:', performance.now() - perf);
});
}
public unlock() {
if(!this.unlockResolve) return;
this.lockPromise = null;
this.unlockResolve();
this.unlockResolve = null;
this.unlockResolve = this.lockPromise = null;
this.processQueue();
}
public async processItem(item: LazyLoadElementBase) {
if(this.lockPromise) {
return;
}
this.inProcess.add(item);
this.log('will load media', this.lockPromise, item);
try {
if(this.lockPromise/* && false */) {
const perf = performance.now();
await this.lockPromise;
this.log('waited lock:', performance.now() - perf);
}
//await new Promise((resolve) => setTimeout(resolve, 2e3));
//await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
await item.load(item.div);
//await item.load(item.div);
await this.loadItem(item);
} catch(err) {
this.log.error('loadMediaQueue error:', err/* , item */);
}
@ -70,25 +79,28 @@ export class LazyLoadQueueBase {
this.log('loaded media', item);
if(this.lazyLoadMedia.length) {
this.processQueue();
}
this.processQueue();
}
protected loadItem(item: LazyLoadElementBase) {
return item.load();
}
protected getItem() {
return this.lazyLoadMedia.shift();
return this.queue.shift();
}
protected addElement(el: LazyLoadElementBase) {
this.processQueue(el);
protected addElement(method: 'push' | 'unshift', el: LazyLoadElementBase) {
this.queue[method](el);
this.processQueue();
}
public async processQueue(item?: LazyLoadElementBase) {
if(this.parallelLimit > 0 && this.inProcess.size >= this.parallelLimit) return;
if(!this.queue.length || this.lockPromise || (this.parallelLimit > 0 && this.inProcess.size >= this.parallelLimit)) return;
do {
if(item) {
this.lazyLoadMedia.findAndSplice(i => i == item);
this.queue.findAndSplice(i => i == item);
} else {
item = this.getItem();
}
@ -100,25 +112,26 @@ export class LazyLoadQueueBase {
}
item = null;
} while(this.inProcess.size < this.parallelLimit && this.lazyLoadMedia.length);
} while(this.inProcess.size < this.parallelLimit && this.queue.length);
}
public push(el: LazyLoadElementBase) {
this.lazyLoadMedia.push(el);
this.addElement(el);
this.addElement('push', el);
}
public unshift(el: LazyLoadElementBase) {
this.lazyLoadMedia.unshift(el);
this.addElement(el);
this.addElement('unshift', el);
}
}
export class LazyLoadQueueIntersector extends LazyLoadQueueBase {
protected queue: Array<LazyLoadElement> = [];
protected inProcess: Set<LazyLoadElement> = new Set();
public intersector: VisibilityIntersector;
protected intersectorTimeout: number;
constructor(protected parallelLimit = 5) {
constructor(protected parallelLimit = PARALLEL_LIMIT) {
super(parallelLimit);
}
@ -146,6 +159,26 @@ export class LazyLoadQueueIntersector extends LazyLoadQueueBase {
this.intersector.refresh();
}
protected loadItem(item: LazyLoadElement) {
return item.load(item.div);
}
protected addElement(method: 'push' | 'unshift', el: LazyLoadElement) {
const item = this.queue.find(i => i.div == el.div);
if(item) {
return false;
} else {
for(const item of this.inProcess) {
if(item.div == el.div) {
return false;
}
}
}
this.queue[method](el);
return true;
}
protected setProcessQueueTimeout() {
if(!this.intersectorTimeout) {
this.intersectorTimeout = window.setTimeout(() => {
@ -154,52 +187,6 @@ export class LazyLoadQueueIntersector extends LazyLoadQueueBase {
}, 0);
}
}
}
export default class LazyLoadQueue extends LazyLoadQueueIntersector {
protected lazyLoadMedia: Array<LazyLoadElement> = [];
protected inProcess: Set<LazyLoadElement> = new Set();
constructor(protected parallelLimit = 5) {
super(parallelLimit);
this.intersector = new VisibilityIntersector(this.onVisibilityChange);
}
private onVisibilityChange = (target: HTMLElement, visible: boolean) => {
if(visible) {
this.log('isIntersecting', target);
// need for set element first if scrolled
const item = this.lazyLoadMedia.findAndSplice(i => i.div == target);
if(item) {
item.wasSeen = true;
this.lazyLoadMedia.unshift(item);
//this.processQueue(item);
}
this.setProcessQueueTimeout();
}
};
protected getItem() {
return this.lazyLoadMedia.findAndSplice(item => item.wasSeen);
}
public async processItem(item: LazyLoadElement) {
await super.processItem(item);
this.intersector.unobserve(item.div);
}
protected addElement(el: LazyLoadElement) {
//super.addElement(el);
if(el.wasSeen) {
super.processQueue(el);
} else {
el.wasSeen = false;
this.intersector.observe(el.div);
}
}
public push(el: LazyLoadElement) {
super.push(el);
@ -210,18 +197,66 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector {
}
}
export class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
private _lazyLoadMedia: Map<HTMLElement, LazyLoadElementBase> = new Map();
export default class LazyLoadQueue extends LazyLoadQueueIntersector {
constructor(protected parallelLimit = PARALLEL_LIMIT) {
super(parallelLimit);
constructor(protected parallelLimit = 5, protected onVisibilityChange?: OnVisibilityChange) {
this.intersector = new VisibilityIntersector(this.onVisibilityChange);
}
private onVisibilityChange = (target: HTMLElement, visible: boolean) => {
if(visible) {
this.log('isIntersecting', target);
// need for set element first if scrolled
const item = this.queue.findAndSplice(i => i.div == target);
if(item) {
item.wasSeen = true;
this.queue.unshift(item);
//this.processQueue(item);
}
this.setProcessQueueTimeout();
}
};
protected getItem() {
return this.queue.findAndSplice(item => item.wasSeen);
}
public async processItem(item: LazyLoadElement) {
await super.processItem(item);
this.intersector.unobserve(item.div);
}
protected addElement(method: 'push' | 'unshift', el: LazyLoadElement) {
const inserted = super.addElement(method, el);
if(!inserted) return false;
this.intersector.observe(el.div);
if(el.wasSeen) {
this.processQueue(el);
} else if(!el.hasOwnProperty('wasSeen')) {
el.wasSeen = false;
}
return true;
}
}
export class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
private _queue: Map<HTMLElement, LazyLoadElement> = new Map();
constructor(protected parallelLimit = PARALLEL_LIMIT, protected onVisibilityChange?: OnVisibilityChange) {
super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => {
if(visible) {
const item = this.lazyLoadMedia.findAndSplice(i => i.div == target);
this.lazyLoadMedia.unshift(item || this._lazyLoadMedia.get(target));
const item = this.queue.findAndSplice(i => i.div == target);
this.queue.unshift(item || this._queue.get(target));
} else {
this.lazyLoadMedia.findAndSplice(i => i.div == target);
this.queue.findAndSplice(i => i.div == target);
}
this.onVisibilityChange && this.onVisibilityChange(target, visible);
@ -229,6 +264,11 @@ export class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
});
}
public clear() {
super.clear();
this._queue.clear();
}
/* public async processItem(item: LazyLoadElement) {
//await super.processItem(item);
await LazyLoadQueueBase.prototype.processItem.call(this, item);
@ -238,8 +278,28 @@ export class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
}
} */
public observe(el: LazyLoadElementBase) {
this._lazyLoadMedia.set(el.div, el);
public observe(el: LazyLoadElement) {
this._queue.set(el.div, el);
this.intersector.observe(el.div);
}
}
export class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
constructor(protected parallelLimit = PARALLEL_LIMIT, protected onVisibilityChange?: OnVisibilityChange) {
super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => {
const item = this.queue.findAndSplice(i => i.div == target);
if(visible && item) {
this.queue.unshift(item);
}
this.onVisibilityChange && this.onVisibilityChange(target, visible);
this.setProcessQueueTimeout();
});
}
public observe(el: HTMLElement) {
this.intersector.observe(el);
}
}

View File

@ -1,6 +1,7 @@
import { logger, LogLevels } from "../lib/logger";
import smoothscroll from '../vendor/smoothscroll';
import { touchSupport, isSafari, mediaSizes } from "../lib/config";
import { CancellablePromise, deferredPromise } from "../lib/polyfill";
//import { isInDOM } from "../lib/utils";
(window as any).__forceSmoothScrollPolyfill__ = true;
smoothscroll.polyfill();
@ -80,6 +81,7 @@ export default class Scrollable {
private onScrolledBottomFired = false; */
public scrollLocked = 0;
public scrollLockedPromise: CancellablePromise<void> = Promise.resolve();
public isVisible = false;
private reorderTimeout: number;
@ -209,13 +211,11 @@ export default class Scrollable {
throw new Error('no side for scroll');
}
const binded = this.onScroll.bind(this);
window.addEventListener('resize', () => {
this.overflowContainer = mediaSizes.isMobile && false ? document.documentElement : this.container;
this.onScroll();
});
this.container.addEventListener('scroll', binded, {passive: true, capture: true});
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
//document.documentElement.addEventListener('scroll', binded, {passive: true, capture: true});
//window.addEventListener('scroll', binded, {passive: true, capture: true});
@ -289,7 +289,7 @@ export default class Scrollable {
this.log('setVirtualContainer:', el, this);
}
public onScroll() {
public onScroll = () => {
/* let scrollTop = this.scrollTop;
this.lastScrollDirection = this.lastScrollTop < scrollTop;
this.lastScrollTop = scrollTop;
@ -313,7 +313,7 @@ export default class Scrollable {
if(this.splitUp) {
clearTimeout(this.disableHoverTimeout);
this.disableHoverTimeout = setTimeout(() => {
this.disableHoverTimeout = window.setTimeout(() => {
//appendTo.classList.remove('disable-hover');
this.lastScrollDirection = 0;
}, 100);
@ -342,7 +342,7 @@ export default class Scrollable {
this.lastScrollDirection = 0;
}
});
}
};
public checkForTriggers(container: HTMLElement) {
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
@ -369,7 +369,7 @@ export default class Scrollable {
public reorder() {
if(!this.splitUp || this.reorderTimeout) return;
this.reorderTimeout = setTimeout(() => {
this.reorderTimeout = window.setTimeout(() => {
this.reorderTimeout = 0;
(Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => {
@ -466,9 +466,15 @@ export default class Scrollable {
}
if(this.scrollLocked) clearTimeout(this.scrollLocked);
this.scrollLocked = setTimeout(() => {
else {
this.scrollLockedPromise = deferredPromise<void>();
}
this.scrollLocked = window.setTimeout(() => {
this.scrollLocked = 0;
this.onScroll();
this.scrollLockedPromise.resolve();
//this.onScroll();
this.container.dispatchEvent(new CustomEvent('scroll'));
}, 468);
this.container.scrollTo({behavior: smooth ? 'smooth' : 'auto', top});

View File

@ -7,6 +7,8 @@ import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebar
import { $rootScope } from "../../lib/utils";
import SearchInput from "../searchInput";
// TODO: поиск по людям глобальный, если не нашло в контактах никого
export default class AppContactsTab implements SliderTab {
private container = document.getElementById('contacts-container');
private list = this.container.querySelector('#contacts') as HTMLUListElement;
@ -58,7 +60,13 @@ export default class AppContactsTab implements SliderTab {
}
const contacts = [..._contacts];
contacts.findAndSplice(u => u == $rootScope.myID);
if(!query) {
contacts.findAndSplice(u => u == $rootScope.myID);
}
/* if(query && 'saved messages'.includes(query.toLowerCase())) {
contacts.unshift($rootScope.myID);
} */
let sorted = contacts
.map(userID => {

View File

@ -21,7 +21,6 @@ export default class AppGifsTab implements SliderTab {
private searchInput: SearchInput;
private gifsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private lazyLoadQueue: LazyLoadQueue;
private nextOffset = '';
private loadedAll = false;
@ -35,9 +34,7 @@ export default class AppGifsTab implements SliderTab {
this.scrollable = new Scrollable(this.contentDiv, 'y', ANIMATIONGROUP, undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.gifsDiv);
this.masonry = new GifsMasonry(this.gifsDiv);
this.lazyLoadQueue = new LazyLoadQueue();
this.masonry = new GifsMasonry(this.gifsDiv, ANIMATIONGROUP, this.scrollable);
this.searchInput = new SearchInput('Search GIFs', (value) => {
this.reset();
@ -76,7 +73,7 @@ export default class AppGifsTab implements SliderTab {
this.searchPromise = null;
this.nextOffset = '';
this.loadedAll = false;
this.lazyLoadQueue.clear();
this.masonry.lazyLoadQueue.clear();
}
public init() {
@ -117,7 +114,7 @@ export default class AppGifsTab implements SliderTab {
if(results.length) {
results.forEach((result) => {
if(result._ === 'botInlineMediaResult' && result.document) {
this.masonry.add(result.document as MyDocument, ANIMATIONGROUP, this.lazyLoadQueue);
this.masonry.add(result.document as MyDocument);
}
});
} else {
@ -125,7 +122,8 @@ export default class AppGifsTab implements SliderTab {
}
this.scrollable.onScroll();
} catch (err) {
} catch(err) {
this.searchPromise = null;
throw new Error(JSON.stringify(err));
}
}

View File

@ -210,6 +210,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
//console.log('loaded doc:', doc, doc.url, container);
const deferred = deferredPromise<void>();
//if(doc.type == 'gif'/* || true */) {
video.addEventListener('canplay', () => {
if(img?.parentElement) {
@ -222,9 +224,16 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
if(doc.type == 'gif' && group) {
animationIntersector.addAnimation(video, group);
}
// test lazyLoadQueue
//setTimeout(() => {
deferred.resolve();
//}, 5000);
}, {once: true});
//}
video.addEventListener('error', deferred.reject);
//if(doc.type != 'round') {
renderImageFromUrl(video, doc.url);
//}
@ -243,6 +252,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
video.dataset.overlay = '1';
new VideoPlayer(video);
}
return deferred;
};
/* if(doc.size >= 20e6 && !doc.downloaded) {
@ -263,8 +274,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
return;
} */
/* doc.downloaded || */!lazyLoadQueue/* && false */ ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */});
return video;
return /* doc.downloaded || */!lazyLoadQueue/* && false */ ? loadVideo() : (lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */}), Promise.resolve());
}
export const formatDate = (timestamp: number, monthShort = false, withYear = true) => {
@ -476,7 +486,7 @@ export function wrapPhoto(photo: MyPhoto | MyDocument, message: any, container:
});
};
return cacheContext.downloaded || !lazyLoadQueue ? load() : lazyLoadQueue.push({div: container, load: load, wasSeen: true});
return cacheContext.downloaded || !lazyLoadQueue ? load() : (lazyLoadQueue.push({div: container, load: load, wasSeen: true}), Promise.resolve());
}
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {

View File

@ -20,7 +20,7 @@ export class AppInlineBotsManager {
query: query,
geo_point: geo && {_: 'inputGeoPoint', lat: geo['lat'], long: geo['long']},
offset
}, {timeout: 1, stopTime: -1, noErrorBox: true}).then(botResults => {
}, {/* timeout: 1, */stopTime: -1, noErrorBox: true}).then(botResults => {
const queryID = botResults.query_id;
/* delete botResults._;
delete botResults.flags;

View File

@ -801,7 +801,8 @@ export class AppImManager {
public onScroll(e: Event) {
if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF);
//if(this.scrollable.scrollLocked) return;
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
if(this.scrollable.scrollLocked && this.scrolledDown) return;
this.onScrollRAF = window.requestAnimationFrame(() => {
//lottieLoader.checkAnimations(false, 'chat');

View File

@ -1073,10 +1073,7 @@ export class AppMediaViewer {
return promise;
};
this.lazyLoadQueue.unshift({
div: null,
load
});
this.lazyLoadQueue.unshift({load});
//} else createPlayer();
});
} else {
@ -1137,10 +1134,7 @@ export class AppMediaViewer {
return cancellablePromise;
};
this.lazyLoadQueue.unshift({
div: null,
load
});
this.lazyLoadQueue.unshift({load});
});
}

View File

@ -97,13 +97,11 @@ export class AppPeersManager {
: appChatsManager.getChat(-peerID)
}
public getPeerID(peerString: any): number {
public getPeerID(peerString: any/* Peer | number | string */): number {
if(typeof(peerString) === 'number') return peerString;
else if(isObject(peerString)) {
return peerString.user_id
? peerString.user_id
: -(peerString.channel_id || peerString.chat_id);
} else if(!peerString) return 0;
else if(isObject(peerString)) return peerString.user_id ? peerString.user_id : -(peerString.channel_id || peerString.chat_id);
else if(!peerString) return 0;
const isUser = peerString.charAt(0) == 'u';
const peerParams = peerString.substr(1).split('_');

View File

@ -86,6 +86,7 @@ export class AppSidebarLeft extends SidebarSlider {
//private log = logger('SL');
private searchGroups = {
//saved: new SearchGroup('', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Global Search', 'messages'),

View File

@ -179,7 +179,7 @@ export class AppUsersManager {
return this.fillContacts().then(_contactsList => {
let contactsList = [..._contactsList];
if(query) {
const results: any = searchIndexManager.search(query, this.contactsIndex);
const results = searchIndexManager.search(query, this.contactsIndex);
const filteredContactsList = [...contactsList].filter(id => !!results[id]);
contactsList = filteredContactsList;
@ -589,7 +589,7 @@ export class AppUsersManager {
return apiManager.invokeApi('contacts.search', {
q: query,
limit
}).then((peers: any) => {
}).then((peers) => {
//console.log(peers);
this.saveApiUsers(peers.users);
appChatsManager.saveApiChats(peers.chats);