Global search 'saved messages'
Fix blinking down arrown Lazy load queue fixes GIFs for Chrome
This commit is contained in:
parent
0333ecfcce
commit
e2280112b2
@ -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);
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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});
|
||||
|
@ -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 => {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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}: {
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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('_');
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user