Browse Source

Global search 'saved messages'

Fix blinking down arrown
Lazy load queue fixes
GIFs for Chrome
master
morethanwords 4 years ago
parent
commit
e2280112b2
  1. 6
      src/components/animationIntersector.ts
  2. 67
      src/components/appSearch.ts
  3. 4
      src/components/emoticonsDropdown/tabs/gifs.ts
  4. 24
      src/components/emoticonsDropdown/tabs/stickers.ts
  5. 212
      src/components/gifsMasonry.ts
  6. 176
      src/components/lazyLoadQueue.ts
  7. 24
      src/components/scrollable_new.ts
  8. 10
      src/components/sidebarLeft/contacts.ts
  9. 12
      src/components/sidebarRight/gifs.ts
  10. 16
      src/components/wrappers.ts
  11. 2
      src/lib/appManagers/AppInlineBotsManager.ts
  12. 3
      src/lib/appManagers/appImManager.ts
  13. 10
      src/lib/appManagers/appMediaViewer.ts
  14. 10
      src/lib/appManagers/appPeersManager.ts
  15. 1
      src/lib/appManagers/appSidebarLeft.ts
  16. 4
      src/lib/appManagers/appUsersManager.ts

6
src/components/animationIntersector.ts

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

67
src/components/appSearch.ts

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

4
src/components/emoticonsDropdown/tabs/gifs.ts

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

24
src/components/emoticonsDropdown/tabs/stickers.ts

@ -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(() => { emoticonsDropdown.events.onClose.push(() => {
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
}); });

212
src/components/gifsMasonry.ts

@ -1,19 +1,151 @@
import { calcImageInBox, findUpClassName } from "../lib/utils"; import { calcImageInBox } from "../lib/utils";
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager"; import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { wrapVideo } from "./wrappers"; import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc"; 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 width = 400;
const maxSingleWidth = width - 100; const maxSingleWidth = width - 100;
const height = 100; const height = 100;
export default class GifsMasonry { 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 gifWidth = doc.w;
let gifHeight = doc.h; let gifHeight = doc.h;
if(gifHeight < height) { if(gifHeight < height) {
@ -21,8 +153,8 @@ export default class GifsMasonry {
gifHeight = height; gifHeight = height;
} }
let willUseWidth = Math.min(maxSingleWidth, width, gifWidth); const willUseWidth = Math.min(maxSingleWidth, width, gifWidth);
let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height); const {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
/* wastedWidth += w; /* wastedWidth += w;
@ -37,7 +169,7 @@ export default class GifsMasonry {
//console.log('gif:', gif, w, h); //console.log('gif:', gif, w, h);
let div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('gif', 'fade-in-transition'); div.classList.add('gif', 'fade-in-transition');
div.style.width = w + 'px'; div.style.width = w + 'px';
div.style.opacity = '0'; div.style.opacity = '0';
@ -46,6 +178,9 @@ export default class GifsMasonry {
this.element.append(div); this.element.append(div);
//this.lazyLoadQueue.observe({div, load: this.processVisibleDiv});
this.lazyLoadQueue.observe(div);
//let preloader = new ProgressivePreloader(div); //let preloader = new ProgressivePreloader(div);
const gotThumb = appDocsManager.getThumb(doc, false); 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 = () => { const afterRender = () => {
if(img) { if(img) {
div.append(img); div.append(img);
div.style.opacity = ''; 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()); (gotThumb?.thumb?.url ? renderImageFromUrl(img, gotThumb.thumb.url, afterRender) : afterRender());

176
src/components/lazyLoadQueue.ts

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

24
src/components/scrollable_new.ts

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

10
src/components/sidebarLeft/contacts.ts

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

12
src/components/sidebarRight/gifs.ts

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

16
src/components/wrappers.ts

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

2
src/lib/appManagers/AppInlineBotsManager.ts

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

3
src/lib/appManagers/appImManager.ts

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

10
src/lib/appManagers/appMediaViewer.ts

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

10
src/lib/appManagers/appPeersManager.ts

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

1
src/lib/appManagers/appSidebarLeft.ts

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

4
src/lib/appManagers/appUsersManager.ts

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

Loading…
Cancel
Save