Fix avatar duplicate
Fix media viewer list again Supported media browser controls
This commit is contained in:
parent
2b25774775
commit
344b70dc3e
@ -8,12 +8,19 @@ import rootScope from "../lib/rootScope";
|
|||||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||||
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
|
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
|
||||||
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
||||||
import { isSafari } from "../helpers/userAgent";
|
import { isApple, isSafari } from "../helpers/userAgent";
|
||||||
import { MOUNT_CLASS_TO } from "../config/debug";
|
import { MOUNT_CLASS_TO } from "../config/debug";
|
||||||
import appDownloadManager from "../lib/appManagers/appDownloadManager";
|
import appDownloadManager from "../lib/appManagers/appDownloadManager";
|
||||||
import simulateEvent from "../helpers/dom/dispatchEvent";
|
import simulateEvent from "../helpers/dom/dispatchEvent";
|
||||||
import type { SearchSuperContext } from "./appSearchSuper.";
|
import type { SearchSuperContext } from "./appSearchSuper.";
|
||||||
import { copy, deepEqual } from "../helpers/object";
|
import { copy, deepEqual } from "../helpers/object";
|
||||||
|
import { DocumentAttribute, Message, MessageMedia, PhotoSize } from "../layer";
|
||||||
|
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
||||||
|
import { isTouchSupported } from "../helpers/touchSupport";
|
||||||
|
import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
|
||||||
|
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||||
|
import I18n from "../lib/langPack";
|
||||||
|
import { SearchListLoader } from "./appMediaViewer";
|
||||||
|
|
||||||
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
|
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
|
||||||
|
|
||||||
@ -21,7 +28,7 @@ import { copy, deepEqual } from "../helpers/object";
|
|||||||
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
|
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
|
||||||
// TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце
|
// TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце
|
||||||
|
|
||||||
type MediaItem = {mid: number, peerId: number};
|
export type MediaItem = {mid: number, peerId: number};
|
||||||
|
|
||||||
type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
|
type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
|
||||||
|
|
||||||
@ -33,6 +40,8 @@ const SHOULD_USE_SAFARI_FIX = (() => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const SEEK_OFFSET = 10;
|
||||||
|
|
||||||
class AppMediaPlaybackController {
|
class AppMediaPlaybackController {
|
||||||
private container: HTMLElement;
|
private container: HTMLElement;
|
||||||
private media: {
|
private media: {
|
||||||
@ -40,7 +49,7 @@ class AppMediaPlaybackController {
|
|||||||
[mid: string]: HTMLMediaElement
|
[mid: string]: HTMLMediaElement
|
||||||
}
|
}
|
||||||
} = {};
|
} = {};
|
||||||
public playingMedia: HTMLMediaElement;
|
private playingMedia: HTMLMediaElement;
|
||||||
|
|
||||||
private waitingMediaForLoad: {
|
private waitingMediaForLoad: {
|
||||||
[peerId: string]: {
|
[peerId: string]: {
|
||||||
@ -49,20 +58,41 @@ class AppMediaPlaybackController {
|
|||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
public willBePlayedMedia: HTMLMediaElement;
|
public willBePlayedMedia: HTMLMediaElement;
|
||||||
public searchContext: SearchSuperContext;
|
private searchContext: SearchSuperContext;
|
||||||
|
|
||||||
private currentPeerId: number;
|
private listLoader: SearchListLoader<MediaItem>;
|
||||||
private prevMid: number;
|
|
||||||
private nextMid: number;
|
|
||||||
|
|
||||||
private prev: MediaItem[] = [];
|
|
||||||
private next: MediaItem[] = [];
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
|
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
|
||||||
this.container.style.cssText = 'display: none;';
|
this.container.style.cssText = 'display: none;';
|
||||||
document.body.append(this.container);
|
document.body.append(this.container);
|
||||||
|
|
||||||
|
if(navigator.mediaSession) {
|
||||||
|
navigator.mediaSession.setActionHandler('play', this.play);
|
||||||
|
navigator.mediaSession.setActionHandler('pause', this.pause);
|
||||||
|
navigator.mediaSession.setActionHandler('stop', this.stop);
|
||||||
|
navigator.mediaSession.setActionHandler('seekbackward', (details) => {
|
||||||
|
const media = this.playingMedia
|
||||||
|
if(media) {
|
||||||
|
media.currentTime = Math.max(0, media.currentTime - (details.seekOffset || SEEK_OFFSET));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler('seekforward', (details) => {
|
||||||
|
const media = this.playingMedia
|
||||||
|
if(media) {
|
||||||
|
media.currentTime = Math.min(media.duration, media.currentTime + (details.seekOffset || SEEK_OFFSET));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler('seekto', (details) => {
|
||||||
|
const media = this.playingMedia
|
||||||
|
if(media) {
|
||||||
|
media.currentTime = details.seekTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', this.previous);
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', this.next);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMedia(peerId: number, doc: MyDocument, mid: number, autoload = true): HTMLMediaElement {
|
public addMedia(peerId: number, doc: MyDocument, mid: number, autoload = true): HTMLMediaElement {
|
||||||
@ -187,33 +217,126 @@ class AppMediaPlaybackController {
|
|||||||
media.safariBuffering = value;
|
media.safariBuffering = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async setNewMediadata(message: Message.message) {
|
||||||
|
const playingMedia = this.playingMedia;
|
||||||
|
const doc = (message.media as MessageMedia.messageMediaDocument).document as MyDocument;
|
||||||
|
|
||||||
|
const artwork: MediaImage[] = [];
|
||||||
|
|
||||||
|
const isVoice = doc.type === 'voice' || doc.type === 'round';
|
||||||
|
let title = '', artist = '';
|
||||||
|
|
||||||
|
if(doc.thumbs?.length) {
|
||||||
|
const size = doc.thumbs[doc.thumbs.length - 1];
|
||||||
|
if(!(size as PhotoSize.photoStrippedSize).bytes) {
|
||||||
|
const cacheContext = appDownloadManager.getCacheContext(doc, size.type);
|
||||||
|
|
||||||
|
if(cacheContext.url) {
|
||||||
|
artwork.push({
|
||||||
|
src: cacheContext.url,
|
||||||
|
sizes: `${(size as PhotoSize.photoSize).w}x${(size as PhotoSize.photoSize).h}`,
|
||||||
|
type: 'image/jpeg'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const download = appPhotosManager.preloadPhoto(doc, size);
|
||||||
|
download.then(() => {
|
||||||
|
if(this.playingMedia !== playingMedia || !cacheContext.url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setNewMediadata(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(isVoice) {
|
||||||
|
const peerId = message.fromId || message.peerId;
|
||||||
|
const peerPhoto = appPeersManager.getPeerPhoto(peerId);
|
||||||
|
const result = appAvatarsManager.loadAvatar(peerId, peerPhoto, 'photo_small');
|
||||||
|
if(result.cached) {
|
||||||
|
const url = await result.loadPromise;
|
||||||
|
artwork.push({
|
||||||
|
src: url,
|
||||||
|
sizes: '160x160',
|
||||||
|
type: 'image/jpeg'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result.loadPromise.then((url) => {
|
||||||
|
if(this.playingMedia !== playingMedia || !url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setNewMediadata(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
title = appPeersManager.getPeerTitle(peerId, true, false);
|
||||||
|
artist = I18n.format(doc.type === 'voice' ? 'AttachAudio' : 'AttachRound', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isVoice) {
|
||||||
|
const attribute = doc.attributes.find(attribute => attribute._ === 'documentAttributeAudio') as DocumentAttribute.documentAttributeAudio;
|
||||||
|
title = attribute && attribute.title || doc.file_name;
|
||||||
|
artist = attribute && attribute.performer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!artwork.length) {
|
||||||
|
if(isApple) {
|
||||||
|
if(isTouchSupported) {
|
||||||
|
artwork.push({
|
||||||
|
src: `assets/img/apple-touch-icon-precomposed.png`,
|
||||||
|
sizes: '180x180',
|
||||||
|
type: 'image/png'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
artwork.push({
|
||||||
|
src: `assets/img/apple-touch-icon.png`,
|
||||||
|
sizes: '180x180',
|
||||||
|
type: 'image/png'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[72, 96, 144, 192, 256, 384, 512].forEach(size => {
|
||||||
|
const sizes = `${size}x${size}`;
|
||||||
|
artwork.push({
|
||||||
|
src: `assets/img/android-chrome-${sizes}.png`,
|
||||||
|
sizes,
|
||||||
|
type: 'image/png'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = new MediaMetadata({
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
artwork
|
||||||
|
});
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
onPlay = (e?: Event) => {
|
onPlay = (e?: Event) => {
|
||||||
const media = e.target as HTMLMediaElement;
|
const media = e.target as HTMLMediaElement;
|
||||||
const peerId = +media.dataset.peerId;
|
const peerId = +media.dataset.peerId;
|
||||||
const mid = +media.dataset.mid;
|
const mid = +media.dataset.mid;
|
||||||
this.currentPeerId = peerId;
|
|
||||||
|
|
||||||
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
|
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
|
||||||
|
|
||||||
|
const message = appMessagesManager.getMessageByPeer(peerId, mid);
|
||||||
|
|
||||||
const previousMedia = this.playingMedia;
|
const previousMedia = this.playingMedia;
|
||||||
if(previousMedia !== media) {
|
if(previousMedia !== media) {
|
||||||
if(previousMedia) {
|
this.stop();
|
||||||
if(!previousMedia.paused) {
|
|
||||||
previousMedia.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset media
|
|
||||||
previousMedia.currentTime = 0;
|
|
||||||
simulateEvent(previousMedia, 'ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.playingMedia = media;
|
this.playingMedia = media;
|
||||||
this.loadSiblingsMedia(peerId, mid);
|
|
||||||
|
if('mediaSession' in navigator) {
|
||||||
|
this.setNewMediadata(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// audio_pause не успеет сработать без таймаута
|
// audio_pause не успеет сработать без таймаута
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const message = appMessagesManager.getMessageByPeer(peerId, mid);
|
|
||||||
rootScope.dispatchEvent('audio_play', {peerId, doc: message.media.document, mid});
|
rootScope.dispatchEvent('audio_play', {peerId, doc: message.media.document, mid});
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@ -238,65 +361,80 @@ class AppMediaPlaybackController {
|
|||||||
|
|
||||||
//console.log('on media end');
|
//console.log('on media end');
|
||||||
|
|
||||||
if(this.nextMid) {
|
this.next();
|
||||||
const media = this.media[this.currentPeerId][this.nextMid];
|
|
||||||
|
|
||||||
/* if(isSafari) {
|
|
||||||
media.autoplay = true;
|
|
||||||
} */
|
|
||||||
|
|
||||||
this.resolveWaitingForLoadMedia(this.currentPeerId, this.nextMid);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
media.play()//.catch(() => {});
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private loadSiblingsMedia(offsetPeerId: number, offsetMid: number) {
|
public toggle(play?: boolean) {
|
||||||
const {playingMedia, searchContext} = this;
|
if(!this.playingMedia) {
|
||||||
if(!searchContext) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return appMessagesManager.getSearch({
|
if(play === undefined) {
|
||||||
...searchContext,
|
play = this.playingMedia.paused;
|
||||||
maxId: offsetMid,
|
}
|
||||||
limit: 3,
|
|
||||||
backLimit: 2,
|
|
||||||
}).then(value => {
|
|
||||||
if(this.playingMedia !== playingMedia || this.searchContext !== searchContext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const idx = Math.max(0, value.history.findIndex(message => message.peerId === offsetPeerId && message.mid === offsetMid));
|
|
||||||
const prev = value.history.slice(Math.max(0, idx));
|
|
||||||
const next = value.history.slice(0, idx);
|
|
||||||
|
|
||||||
[this.prevMid, this.nextMid].filter(Boolean).forEach(mid => {
|
if(this.playingMedia.paused !== play) {
|
||||||
const peerId = searchContext.peerId;
|
return;
|
||||||
const message = appMessagesManager.getMessageByPeer(peerId, mid);
|
}
|
||||||
this.addMedia(peerId, message.media.document, mid, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
//console.log('loadSiblingsAudio', audio, type, mid, value, this.prevMid, this.nextMid);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggle() {
|
|
||||||
if(!this.playingMedia) return;
|
|
||||||
|
|
||||||
if(this.playingMedia.paused) {
|
if(play) {
|
||||||
this.playingMedia.play();
|
this.playingMedia.play();
|
||||||
} else {
|
} else {
|
||||||
this.playingMedia.pause();
|
this.playingMedia.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public pause() {
|
public play = () => {
|
||||||
if(!this.playingMedia || this.playingMedia.paused) return;
|
return this.toggle(true);
|
||||||
this.playingMedia.pause();
|
};
|
||||||
}
|
|
||||||
|
public pause = () => {
|
||||||
|
return this.toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
public stop = () => {
|
||||||
|
const media = this.playingMedia;
|
||||||
|
if(media) {
|
||||||
|
if(!media.paused) {
|
||||||
|
media.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
media.currentTime = 0;
|
||||||
|
simulateEvent(media, 'ended');
|
||||||
|
|
||||||
|
// this.playingMedia = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public playItem = (item: MediaItem) => {
|
||||||
|
const {peerId, mid} = item;
|
||||||
|
const media = this.media[peerId][mid];
|
||||||
|
|
||||||
|
/* if(isSafari) {
|
||||||
|
media.autoplay = true;
|
||||||
|
} */
|
||||||
|
|
||||||
|
this.resolveWaitingForLoadMedia(peerId, mid);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
media.play()//.catch(() => {});
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
public next = () => {
|
||||||
|
this.listLoader.go(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
public previous = () => {
|
||||||
|
const media = this.playingMedia;
|
||||||
|
if(media && media.currentTime > 5) {
|
||||||
|
media.currentTime = 0;
|
||||||
|
this.toggle(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listLoader.go(-1);
|
||||||
|
};
|
||||||
|
|
||||||
public willBePlayed(media: HTMLMediaElement) {
|
public willBePlayed(media: HTMLMediaElement) {
|
||||||
this.willBePlayedMedia = media;
|
this.willBePlayedMedia = media;
|
||||||
@ -304,12 +442,43 @@ class AppMediaPlaybackController {
|
|||||||
|
|
||||||
public setSearchContext(context: SearchSuperContext) {
|
public setSearchContext(context: SearchSuperContext) {
|
||||||
if(deepEqual(this.searchContext, context)) {
|
if(deepEqual(this.searchContext, context)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.searchContext = copy(context); // {_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'}
|
this.searchContext = copy(context); // {_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'}
|
||||||
this.prev.length = 0;
|
return true;
|
||||||
this.next.length = 0;
|
}
|
||||||
|
|
||||||
|
public setTargets(current: MediaItem, prev?: MediaItem[], next?: MediaItem[]) {
|
||||||
|
if(!this.listLoader) {
|
||||||
|
this.listLoader = new SearchListLoader({
|
||||||
|
loadCount: 10,
|
||||||
|
loadWhenLeft: 5,
|
||||||
|
processItem: (item: Message.message) => {
|
||||||
|
const {peerId, mid} = item;
|
||||||
|
this.addMedia(peerId, (item.media as MessageMedia.messageMediaDocument).document as MyDocument, mid, false);
|
||||||
|
return {peerId, mid};
|
||||||
|
},
|
||||||
|
onJump: (item, older) => {
|
||||||
|
this.playItem(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.listLoader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const reverse = this.searchContext.folderId !== undefined ? false : true;
|
||||||
|
if(prev) {
|
||||||
|
this.listLoader.setTargets(prev, next, reverse);
|
||||||
|
} else {
|
||||||
|
this.listLoader.reverse = reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listLoader.setSearchContext(this.searchContext);
|
||||||
|
this.listLoader.current = current;
|
||||||
|
|
||||||
|
this.listLoader.load(true);
|
||||||
|
this.listLoader.load(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,9 @@ import appSidebarRight from "./sidebarRight";
|
|||||||
import SwipeHandler from "./swipeHandler";
|
import SwipeHandler from "./swipeHandler";
|
||||||
import { ONE_DAY } from "../helpers/date";
|
import { ONE_DAY } from "../helpers/date";
|
||||||
import { SearchSuperContext } from "./appSearchSuper.";
|
import { SearchSuperContext } from "./appSearchSuper.";
|
||||||
import DEBUG from "../config/debug";
|
|
||||||
import appNavigationController from "./appNavigationController";
|
import appNavigationController from "./appNavigationController";
|
||||||
import { Message } from "../layer";
|
import { Message } from "../layer";
|
||||||
import { forEachReverse } from "../helpers/array";
|
import AppSharedMediaTab, { filterChatPhotosMessages } from "./sidebarRight/tabs/sharedMedia";
|
||||||
import AppSharedMediaTab from "./sidebarRight/tabs/sharedMedia";
|
|
||||||
import findUpClassName from "../helpers/dom/findUpClassName";
|
import findUpClassName from "../helpers/dom/findUpClassName";
|
||||||
import renderImageFromUrl, { renderImageFromUrlPromise } from "../helpers/dom/renderImageFromUrl";
|
import renderImageFromUrl, { renderImageFromUrlPromise } from "../helpers/dom/renderImageFromUrl";
|
||||||
import getVisibleRect from "../helpers/dom/getVisibleRect";
|
import getVisibleRect from "../helpers/dom/getVisibleRect";
|
||||||
@ -53,7 +51,7 @@ import { attachClickEvent } from "../helpers/dom/clickEvent";
|
|||||||
import PopupDeleteMessages from "./popups/deleteMessages";
|
import PopupDeleteMessages from "./popups/deleteMessages";
|
||||||
import RangeSelector from "./rangeSelector";
|
import RangeSelector from "./rangeSelector";
|
||||||
import windowSize from "../helpers/windowSize";
|
import windowSize from "../helpers/windowSize";
|
||||||
import { safeAssign } from "../helpers/object";
|
import ListLoader, { ListLoaderOptions } from "../helpers/listLoader";
|
||||||
|
|
||||||
const ZOOM_STEP = 0.5;
|
const ZOOM_STEP = 0.5;
|
||||||
const ZOOM_INITIAL_VALUE = 1;
|
const ZOOM_INITIAL_VALUE = 1;
|
||||||
@ -66,129 +64,22 @@ const ZOOM_MAX_VALUE = 4;
|
|||||||
|
|
||||||
const MEDIA_VIEWER_CLASSNAME = 'media-viewer';
|
const MEDIA_VIEWER_CLASSNAME = 'media-viewer';
|
||||||
|
|
||||||
type MediaQueueLoaderOptions<Item extends {}> = {
|
export class SearchListLoader<Item extends {mid: number, peerId: number}> extends ListLoader<Item> {
|
||||||
prevTargets?: MediaQueueLoader<Item>['prevTargets'],
|
|
||||||
nextTargets?: MediaQueueLoader<Item>['nextTargets'],
|
|
||||||
onLoadedMore?: MediaQueueLoader<Item>['onLoadedMore'],
|
|
||||||
generateItem?: MediaQueueLoader<Item>['generateItem'],
|
|
||||||
getLoadPromise?: MediaQueueLoader<Item>['getLoadPromise'],
|
|
||||||
reverse?: MediaQueueLoader<Item>['reverse']
|
|
||||||
};
|
|
||||||
|
|
||||||
class MediaQueueLoader<Item extends {}> {
|
|
||||||
public target: Item = false as any;
|
|
||||||
public prevTargets: Item[] = [];
|
|
||||||
public nextTargets: Item[] = [];
|
|
||||||
|
|
||||||
public loadMediaPromiseUp: Promise<void> = null;
|
|
||||||
public loadMediaPromiseDown: Promise<void> = null;
|
|
||||||
public loadedAllMediaUp = false;
|
|
||||||
public loadedAllMediaDown = false;
|
|
||||||
|
|
||||||
public reverse = false; // reverse means next = higher msgid
|
|
||||||
|
|
||||||
protected generateItem: (item: Item) => Item = (item) => item;
|
|
||||||
protected getLoadPromise: (older: boolean, anchor: Item, loadCount: number) => Promise<Item[]>;
|
|
||||||
protected onLoadedMore: () => void;
|
|
||||||
|
|
||||||
constructor(options: MediaQueueLoaderOptions<Item> = {}) {
|
|
||||||
safeAssign(this, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setTargets(prevTargets: Item[], nextTargets: Item[], reverse: boolean) {
|
|
||||||
this.prevTargets = prevTargets;
|
|
||||||
this.nextTargets = nextTargets;
|
|
||||||
this.reverse = reverse;
|
|
||||||
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
|
|
||||||
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset() {
|
|
||||||
this.prevTargets = [];
|
|
||||||
this.nextTargets = [];
|
|
||||||
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
|
|
||||||
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// нет смысла делать проверку для reverse и loadMediaPromise
|
|
||||||
public loadMoreMedia = (older = true) => {
|
|
||||||
//if(!older && this.reverse) return;
|
|
||||||
|
|
||||||
if(older && this.loadedAllMediaDown) return Promise.resolve();
|
|
||||||
else if(!older && this.loadedAllMediaUp) return Promise.resolve();
|
|
||||||
|
|
||||||
if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown;
|
|
||||||
else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp;
|
|
||||||
|
|
||||||
const loadCount = 50;
|
|
||||||
|
|
||||||
let anchor: Item;
|
|
||||||
if(older) {
|
|
||||||
anchor = this.reverse ? this.prevTargets[0] : this.nextTargets[this.nextTargets.length - 1];
|
|
||||||
} else {
|
|
||||||
anchor = this.reverse ? this.nextTargets[this.nextTargets.length - 1] : this.prevTargets[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = this.getLoadPromise(older, anchor, loadCount).then(items => {
|
|
||||||
if((older && this.loadMediaPromiseDown !== promise) || (!older && this.loadMediaPromiseUp !== promise)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(items.length < loadCount) {
|
|
||||||
/* if(this.reverse) {
|
|
||||||
if(older) this.loadedAllMediaUp = true;
|
|
||||||
else this.loadedAllMediaDown = true;
|
|
||||||
} else { */
|
|
||||||
if(older) this.loadedAllMediaDown = true;
|
|
||||||
else this.loadedAllMediaUp = true;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
const method: any = older ? items.forEach.bind(items) : forEachReverse.bind(null, items);
|
|
||||||
method((item: Item) => {
|
|
||||||
const t = this.generateItem(item);
|
|
||||||
if(!t) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(older) {
|
|
||||||
if(this.reverse) this.prevTargets.unshift(t);
|
|
||||||
else this.nextTargets.push(t);
|
|
||||||
} else {
|
|
||||||
if(this.reverse) this.nextTargets.push(t);
|
|
||||||
else this.prevTargets.unshift(t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onLoadedMore && this.onLoadedMore();
|
|
||||||
}, () => {}).then(() => {
|
|
||||||
if(older) this.loadMediaPromiseDown = null;
|
|
||||||
else this.loadMediaPromiseUp = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(older) this.loadMediaPromiseDown = promise;
|
|
||||||
else this.loadMediaPromiseUp = promise;
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class MediaSearchQueueLoader<Item extends {mid: number, peerId: number}> extends MediaQueueLoader<Item> {
|
|
||||||
public searchContext: SearchSuperContext;
|
public searchContext: SearchSuperContext;
|
||||||
|
|
||||||
constructor(options: Omit<MediaQueueLoaderOptions<Item>, 'getLoadPromise'> = {}) {
|
constructor(options: Omit<ListLoaderOptions<Item>, 'loadMore'> = {}) {
|
||||||
super({
|
super({
|
||||||
...options,
|
...options,
|
||||||
getLoadPromise: (older, anchor, loadCount) => {
|
loadMore: (anchor, older, loadCount) => {
|
||||||
const backLimit = older ? 0 : loadCount;
|
const backLimit = older ? 0 : loadCount;
|
||||||
let maxId = this.target?.mid;
|
let maxId = this.current?.mid;
|
||||||
|
|
||||||
if(anchor) maxId = anchor.mid;
|
if(anchor) maxId = anchor.mid;
|
||||||
if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1);
|
if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1);
|
||||||
|
|
||||||
return appMessagesManager.getSearch({
|
return appMessagesManager.getSearch({
|
||||||
...this.searchContext,
|
...this.searchContext,
|
||||||
peerId: anchor?.peerId,
|
peerId: this.searchContext.peerId || anchor?.peerId,
|
||||||
maxId,
|
maxId,
|
||||||
limit: backLimit ? 0 : loadCount,
|
limit: backLimit ? 0 : loadCount,
|
||||||
backLimit
|
backLimit
|
||||||
@ -197,11 +88,15 @@ class MediaSearchQueueLoader<Item extends {mid: number, peerId: number}> extends
|
|||||||
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
|
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') {
|
||||||
|
filterChatPhotosMessages(value);
|
||||||
|
}
|
||||||
|
|
||||||
if(value.next_rate) {
|
if(value.next_rate) {
|
||||||
this.searchContext.nextRate = value.next_rate;
|
this.searchContext.nextRate = value.next_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.history as any;
|
return {count: value.count, items: value.history};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -211,37 +106,45 @@ class MediaSearchQueueLoader<Item extends {mid: number, peerId: number}> extends
|
|||||||
this.searchContext = context;
|
this.searchContext = context;
|
||||||
|
|
||||||
if(this.searchContext.folderId !== undefined) {
|
if(this.searchContext.folderId !== undefined) {
|
||||||
this.loadedAllMediaUp = true;
|
this.loadedAllUp = true;
|
||||||
|
|
||||||
if(this.searchContext.nextRate === undefined) {
|
if(this.searchContext.nextRate === undefined) {
|
||||||
this.loadedAllMediaDown = true;
|
this.loadedAllDown = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') {
|
||||||
|
this.loadedAllUp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset() {
|
||||||
|
super.reset();
|
||||||
|
this.searchContext = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MediaAvatarQueueLoader<Item extends {photoId: string}> extends MediaQueueLoader<Item> {
|
class AvatarListLoader<Item extends {photoId: string}> extends ListLoader<Item> {
|
||||||
private peerId: number;
|
private peerId: number;
|
||||||
|
|
||||||
constructor(options: Omit<MediaQueueLoaderOptions<Item>, 'getLoadPromise'> & {peerId: number}) {
|
constructor(options: Omit<ListLoaderOptions<Item>, 'loadMore'> & {peerId: number}) {
|
||||||
super({
|
super({
|
||||||
...options,
|
...options,
|
||||||
getLoadPromise: (older, anchor, loadCount) => {
|
loadMore: (anchor, older, loadCount) => {
|
||||||
if(this.peerId < 0) return Promise.resolve([]); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
|
if(this.peerId < 0 || !older) return Promise.resolve({count: 0, items: []}); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
|
||||||
|
|
||||||
return appPhotosManager.getUserPhotos(this.peerId, anchor?.photoId, loadCount).then(value => {
|
const maxId = anchor?.photoId;
|
||||||
const idx = value.photos.indexOf(this.target.photoId);
|
return appPhotosManager.getUserPhotos(this.peerId, maxId, loadCount).then(value => {
|
||||||
if(idx !== -1) {
|
const items = value.photos.map(photoId => {
|
||||||
value.photos.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.photos.map(photoId => {
|
|
||||||
return {element: null as HTMLElement, photoId} as any;
|
return {element: null as HTMLElement, photoId} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {count: value.count, items};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.loadedAllUp = true;
|
||||||
this.peerId = options.peerId;
|
this.peerId = options.peerId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,8 +162,6 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
protected preloader: ProgressivePreloader = null;
|
protected preloader: ProgressivePreloader = null;
|
||||||
protected preloaderStreamable: ProgressivePreloader = null;
|
protected preloaderStreamable: ProgressivePreloader = null;
|
||||||
|
|
||||||
protected prevTargets: TargetType[] = [];
|
|
||||||
protected nextTargets: TargetType[] = [];
|
|
||||||
//protected targetContainer: HTMLElement = null;
|
//protected targetContainer: HTMLElement = null;
|
||||||
//protected loadMore: () => void = null;
|
//protected loadMore: () => void = null;
|
||||||
|
|
||||||
@ -268,8 +169,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
|
|
||||||
protected isFirstOpen = true;
|
protected isFirstOpen = true;
|
||||||
|
|
||||||
protected reverse = false; // reverse means next = higher msgid
|
// protected needLoadMore = true;
|
||||||
protected needLoadMore = true;
|
|
||||||
|
|
||||||
protected pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
protected pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||||
|
|
||||||
@ -302,14 +202,14 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
protected ctrlKeyDown: boolean;
|
protected ctrlKeyDown: boolean;
|
||||||
|
|
||||||
get target() {
|
get target() {
|
||||||
return this.queueLoader.target;
|
return this.listLoader.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
set target(value) {
|
set target(value) {
|
||||||
this.queueLoader.target = value;
|
this.listLoader.current = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(protected queueLoader: MediaQueueLoader<TargetType>,
|
constructor(protected listLoader: ListLoader<TargetType>,
|
||||||
topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) {
|
topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) {
|
||||||
this.log = logger('AMV');
|
this.log = logger('AMV');
|
||||||
this.preloader = new ProgressivePreloader();
|
this.preloader = new ProgressivePreloader();
|
||||||
@ -432,30 +332,13 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
el.addEventListener('click', this.close.bind(this));
|
el.addEventListener('click', this.close.bind(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.buttons.prev.addEventListener('click', (e) => {
|
([[-1, this.buttons.prev], [1, this.buttons.next]] as [number, HTMLElement][]).forEach(([moveLength, button]) => {
|
||||||
cancelEvent(e);
|
button.addEventListener('click', (e) => {
|
||||||
if(this.setMoverPromise) return;
|
cancelEvent(e);
|
||||||
|
if(this.setMoverPromise) return;
|
||||||
const target = this.prevTargets.pop();
|
|
||||||
if(target) {
|
this.listLoader.go(moveLength);
|
||||||
this.nextTargets.unshift(this.target);
|
});
|
||||||
this.onPrevClick(target);
|
|
||||||
} else {
|
|
||||||
this.buttons.prev.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.buttons.next.addEventListener('click', (e) => {
|
|
||||||
cancelEvent(e);
|
|
||||||
if(this.setMoverPromise) return;
|
|
||||||
|
|
||||||
let target = this.nextTargets.shift();
|
|
||||||
if(target) {
|
|
||||||
this.prevTargets.push(this.target);
|
|
||||||
this.onNextClick(target);
|
|
||||||
} else {
|
|
||||||
this.buttons.next.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.buttons.zoom.addEventListener('click', () => {
|
this.buttons.zoom.addEventListener('click', () => {
|
||||||
@ -467,6 +350,11 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
|
|
||||||
this.wholeDiv.addEventListener('click', this.onClick);
|
this.wholeDiv.addEventListener('click', this.onClick);
|
||||||
|
|
||||||
|
this.listLoader.onJump = (item, older) => {
|
||||||
|
if(older) this.onNextClick(item);
|
||||||
|
else this.onPrevClick(item);
|
||||||
|
};
|
||||||
|
|
||||||
if(isTouchSupported) {
|
if(isTouchSupported) {
|
||||||
const swipeHandler = new SwipeHandler({
|
const swipeHandler = new SwipeHandler({
|
||||||
element: this.wholeDiv,
|
element: this.wholeDiv,
|
||||||
@ -606,9 +494,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
|
|
||||||
const promise = this.setMoverToTarget(this.target?.element, true).then(({onAnimationEnd}) => onAnimationEnd);
|
const promise = this.setMoverToTarget(this.target?.element, true).then(({onAnimationEnd}) => onAnimationEnd);
|
||||||
|
|
||||||
this.target = false as any;
|
this.listLoader.reset();
|
||||||
this.prevTargets.length = 0;
|
|
||||||
this.nextTargets.length = 0;
|
|
||||||
this.setMoverPromise = null;
|
this.setMoverPromise = null;
|
||||||
this.tempId = -1;
|
this.tempId = -1;
|
||||||
(window as any).appMediaViewer = undefined;
|
(window as any).appMediaViewer = undefined;
|
||||||
@ -1353,7 +1239,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async _openMedia(media: any, timestamp: number, fromId: number, fromRight: number, target?: HTMLElement, reverse = false,
|
protected async _openMedia(media: any, timestamp: number, fromId: number, fromRight: number, target?: HTMLElement, reverse = false,
|
||||||
prevTargets: TargetType[] = [], nextTargets: TargetType[] = [], needLoadMore = true) {
|
prevTargets: TargetType[] = [], nextTargets: TargetType[] = []/* , needLoadMore = true */) {
|
||||||
if(this.setMoverPromise) return this.setMoverPromise;
|
if(this.setMoverPromise) return this.setMoverPromise;
|
||||||
|
|
||||||
/* if(DEBUG) {
|
/* if(DEBUG) {
|
||||||
@ -1367,12 +1253,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
|
|
||||||
if(this.isFirstOpen) {
|
if(this.isFirstOpen) {
|
||||||
//this.targetContainer = targetContainer;
|
//this.targetContainer = targetContainer;
|
||||||
this.prevTargets = prevTargets;
|
// this.needLoadMore = needLoadMore;
|
||||||
this.nextTargets = nextTargets;
|
|
||||||
this.reverse = reverse;
|
|
||||||
this.needLoadMore = needLoadMore;
|
|
||||||
this.isFirstOpen = false;
|
this.isFirstOpen = false;
|
||||||
this.queueLoader.setTargets(this.prevTargets, this.nextTargets, this.reverse);
|
this.listLoader.setTargets(prevTargets, nextTargets, reverse);
|
||||||
(window as any).appMediaViewer = this;
|
(window as any).appMediaViewer = this;
|
||||||
//this.loadMore = loadMore;
|
//this.loadMore = loadMore;
|
||||||
|
|
||||||
@ -1389,8 +1272,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
//if(prevTarget && (!prevTarget.parentElement || !this.isElementVisible(this.targetContainer, prevTarget))) prevTarget = null;
|
//if(prevTarget && (!prevTarget.parentElement || !this.isElementVisible(this.targetContainer, prevTarget))) prevTarget = null;
|
||||||
//if(nextTarget && (!nextTarget.parentElement || !this.isElementVisible(this.targetContainer, nextTarget))) nextTarget = null;
|
//if(nextTarget && (!nextTarget.parentElement || !this.isElementVisible(this.targetContainer, nextTarget))) nextTarget = null;
|
||||||
|
|
||||||
this.buttons.prev.classList.toggle('hide', !this.prevTargets.length);
|
this.buttons.prev.classList.toggle('hide', !this.listLoader.previous.length);
|
||||||
this.buttons.next.classList.toggle('hide', !this.nextTargets.length);
|
this.buttons.next.classList.toggle('hide', !this.listLoader.next.length);
|
||||||
|
|
||||||
const container = this.content.media;
|
const container = this.content.media;
|
||||||
const useContainerAsTarget = !target || target === container;
|
const useContainerAsTarget = !target || target === container;
|
||||||
@ -1399,16 +1282,6 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
|
|||||||
this.target = {element: target} as any;
|
this.target = {element: target} as any;
|
||||||
const tempId = ++this.tempId;
|
const tempId = ++this.tempId;
|
||||||
|
|
||||||
if(this.needLoadMore) {
|
|
||||||
if(this.nextTargets.length < 20) {
|
|
||||||
this.queueLoader.loadMoreMedia(!this.reverse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.prevTargets.length < 20) {
|
|
||||||
this.queueLoader.loadMoreMedia(this.reverse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(container.firstElementChild) {
|
if(container.firstElementChild) {
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
}
|
}
|
||||||
@ -1746,16 +1619,15 @@ type AppMediaViewerTargetType = {
|
|||||||
};
|
};
|
||||||
export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
|
export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
|
||||||
protected btnMenuDelete: HTMLElement;
|
protected btnMenuDelete: HTMLElement;
|
||||||
|
protected listLoader: SearchListLoader<AppMediaViewerTargetType>;
|
||||||
protected queueLoader: MediaSearchQueueLoader<AppMediaViewerTargetType>;
|
|
||||||
|
|
||||||
get searchContext() {
|
get searchContext() {
|
||||||
return this.queueLoader.searchContext;
|
return this.listLoader.searchContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(new MediaSearchQueueLoader({
|
super(new SearchListLoader({
|
||||||
generateItem: (item) => {
|
processItem: (item) => {
|
||||||
const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument';
|
const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument';
|
||||||
const {mid, peerId} = item;
|
const {mid, peerId} = item;
|
||||||
const media: MyPhoto | MyDocument = appMessagesManager.getMediaFromMessage(item);
|
const media: MyPhoto | MyDocument = appMessagesManager.getMediaFromMessage(item);
|
||||||
@ -1950,13 +1822,13 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setSearchContext(context: SearchSuperContext) {
|
public setSearchContext(context: SearchSuperContext) {
|
||||||
this.queueLoader.setSearchContext(context);
|
this.listLoader.setSearchContext(context);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openMedia(message: any, target?: HTMLElement, fromRight = 0, reverse = false,
|
public async openMedia(message: any, target?: HTMLElement, fromRight = 0, reverse = false,
|
||||||
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) {
|
prevTargets: AppMediaViewerTargetType[] = [], nextTargets: AppMediaViewerTargetType[] = []/* , needLoadMore = true */) {
|
||||||
if(this.setMoverPromise) return this.setMoverPromise;
|
if(this.setMoverPromise) return this.setMoverPromise;
|
||||||
|
|
||||||
const mid = message.mid;
|
const mid = message.mid;
|
||||||
@ -1971,7 +1843,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.setCaption(message);
|
this.setCaption(message);
|
||||||
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore);
|
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets/* , needLoadMore */);
|
||||||
this.target.mid = mid;
|
this.target.mid = mid;
|
||||||
this.target.peerId = message.peerId;
|
this.target.peerId = message.peerId;
|
||||||
|
|
||||||
@ -1988,7 +1860,7 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe
|
|||||||
public peerId: number;
|
public peerId: number;
|
||||||
|
|
||||||
constructor(peerId: number) {
|
constructor(peerId: number) {
|
||||||
super(new MediaAvatarQueueLoader({peerId}), [/* 'delete' */]);
|
super(new AvatarListLoader({peerId}), [/* 'delete' */]);
|
||||||
|
|
||||||
this.peerId = peerId;
|
this.peerId = peerId;
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { RichTextProcessor } from "../lib/richtextprocessor";
|
|||||||
import { formatDate, wrapPhoto } from "./wrappers";
|
import { formatDate, wrapPhoto } from "./wrappers";
|
||||||
import ProgressivePreloader from "./preloader";
|
import ProgressivePreloader from "./preloader";
|
||||||
import { MediaProgressLine } from "../lib/mediaPlayer";
|
import { MediaProgressLine } from "../lib/mediaPlayer";
|
||||||
import appMediaPlaybackController from "./appMediaPlaybackController";
|
import appMediaPlaybackController, { MediaItem } from "./appMediaPlaybackController";
|
||||||
import { DocumentAttribute } from "../layer";
|
import { DocumentAttribute } from "../layer";
|
||||||
import mediaSizes from "../helpers/mediaSizes";
|
import mediaSizes from "../helpers/mediaSizes";
|
||||||
import { isSafari } from "../helpers/userAgent";
|
import { isSafari } from "../helpers/userAgent";
|
||||||
@ -21,13 +21,14 @@ import { formatDateAccordingToToday } from "../helpers/date";
|
|||||||
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
import { cancelEvent } from "../helpers/dom/cancelEvent";
|
||||||
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
|
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
|
||||||
import LazyLoadQueue from "./lazyLoadQueue";
|
import LazyLoadQueue from "./lazyLoadQueue";
|
||||||
import { deferredPromise } from "../helpers/cancellablePromise";
|
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
||||||
import ListenerSetter, { Listener } from "../helpers/listenerSetter";
|
import ListenerSetter, { Listener } from "../helpers/listenerSetter";
|
||||||
import noop from "../helpers/noop";
|
import noop from "../helpers/noop";
|
||||||
|
import findUpClassName from "../helpers/dom/findUpClassName";
|
||||||
|
|
||||||
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
||||||
mids.forEach(mid => {
|
mids.forEach(mid => {
|
||||||
(Array.from(document.querySelectorAll('audio-element[message-id="' + mid + '"][peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => {
|
(Array.from(document.querySelectorAll('audio-element[data-mid="' + mid + '"][data-peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => {
|
||||||
elem.classList.remove('is-unread');
|
elem.classList.remove('is-unread');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -371,7 +372,7 @@ export default class AudioElement extends HTMLElement {
|
|||||||
private listenerSetter = new ListenerSetter();
|
private listenerSetter = new ListenerSetter();
|
||||||
private onTypeDisconnect: () => void;
|
private onTypeDisconnect: () => void;
|
||||||
public onLoad: (autoload?: boolean) => void;
|
public onLoad: (autoload?: boolean) => void;
|
||||||
readyPromise: import("/Users/kuzmenko/Documents/projects/tweb/src/helpers/cancellablePromise").CancellablePromise<void>;
|
private readyPromise: CancellablePromise<void>;
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
this.classList.add('audio');
|
this.classList.add('audio');
|
||||||
@ -429,7 +430,22 @@ export default class AudioElement extends HTMLElement {
|
|||||||
e && cancelEvent(e);
|
e && cancelEvent(e);
|
||||||
|
|
||||||
if(paused) {
|
if(paused) {
|
||||||
appMediaPlaybackController.setSearchContext(this.searchContext);
|
if(appMediaPlaybackController.setSearchContext(this.searchContext)) {
|
||||||
|
let prev: MediaItem[], next: MediaItem[];
|
||||||
|
const container = findUpClassName(this, this.classList.contains('search-super-item') ? 'tabs-tab' : 'bubbles-inner');
|
||||||
|
if(container) {
|
||||||
|
const elements = Array.from(container.querySelectorAll('.audio' + (isVoice ? '.is-voice' : ''))) as AudioElement[];
|
||||||
|
const idx = elements.indexOf(this);
|
||||||
|
|
||||||
|
const mediaItems: MediaItem[] = elements.map(element => ({peerId: +element.dataset.peerId, mid: +element.dataset.mid}));
|
||||||
|
|
||||||
|
prev = mediaItems.slice(0, idx);
|
||||||
|
next = mediaItems.slice(idx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
|
||||||
|
}
|
||||||
|
|
||||||
audio.play().catch(() => {});
|
audio.play().catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
audio.pause();
|
audio.pause();
|
||||||
@ -444,7 +460,7 @@ export default class AudioElement extends HTMLElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addAudioListener('timeupdate', () => {
|
this.addAudioListener('timeupdate', () => {
|
||||||
if(appMediaPlaybackController.playingMedia !== audio || appMediaPlaybackController.isSafariBuffering(audio)) return;
|
if((!audio.currentTime && audio.paused) || appMediaPlaybackController.isSafariBuffering(audio)) return;
|
||||||
audioTimeDiv.innerText = getTimeStr();
|
audioTimeDiv.innerText = getTimeStr();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -295,11 +295,10 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(message.media?.document) {
|
if(message.media?.document) {
|
||||||
const element = bubble.querySelector(`audio-element[message-id="${tempId}"], .document[data-doc-id="${tempId}"]`) as HTMLElement;
|
const element = bubble.querySelector(`audio-element[data-mid="${tempId}"], .document[data-doc-id="${tempId}"]`) as HTMLElement;
|
||||||
if(element) {
|
if(element) {
|
||||||
if(element instanceof AudioElement) {
|
if(element instanceof AudioElement) {
|
||||||
element.setAttribute('doc-id', message.media.document.id);
|
element.dataset.mid = '' + mid;
|
||||||
element.setAttribute('message-id', '' + mid);
|
|
||||||
element.message = message;
|
element.message = message;
|
||||||
element.onLoad(true);
|
element.onLoad(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -87,7 +87,7 @@ export class LazyLoadQueueBase {
|
|||||||
//await item.load(item.div);
|
//await item.load(item.div);
|
||||||
await this.loadItem(item);
|
await this.loadItem(item);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(err)) {
|
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(err as string)) {
|
||||||
this.log.error('loadMediaQueue error:', err/* , item */);
|
this.log.error('loadMediaQueue error:', err/* , item */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
|
|||||||
import findUpTag from "../../../helpers/dom/findUpTag";
|
import findUpTag from "../../../helpers/dom/findUpTag";
|
||||||
import replaceContent from "../../../helpers/dom/replaceContent";
|
import replaceContent from "../../../helpers/dom/replaceContent";
|
||||||
import ListenerSetter from "../../../helpers/listenerSetter";
|
import ListenerSetter from "../../../helpers/listenerSetter";
|
||||||
import ScrollableLoader from "../../../helpers/listLoader";
|
import ScrollableLoader from "../../../helpers/scrollableLoader";
|
||||||
import { ChannelParticipant, Chat, ChatBannedRights, Update } from "../../../layer";
|
import { ChannelParticipant, Chat, ChatBannedRights, Update } from "../../../layer";
|
||||||
import appChatsManager, { ChatRights } from "../../../lib/appManagers/appChatsManager";
|
import appChatsManager, { ChatRights } from "../../../lib/appManagers/appChatsManager";
|
||||||
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
|
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import appImManager from "../../../lib/appManagers/appImManager";
|
import appImManager from "../../../lib/appManagers/appImManager";
|
||||||
import appMessagesManager, { AppMessagesManager } from "../../../lib/appManagers/appMessagesManager";
|
import appMessagesManager, { AppMessagesManager, MyMessage } from "../../../lib/appManagers/appMessagesManager";
|
||||||
import appPeersManager from "../../../lib/appManagers/appPeersManager";
|
import appPeersManager from "../../../lib/appManagers/appPeersManager";
|
||||||
import appProfileManager from "../../../lib/appManagers/appProfileManager";
|
import appProfileManager from "../../../lib/appManagers/appProfileManager";
|
||||||
import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager";
|
import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager";
|
||||||
@ -31,8 +31,6 @@ import Row from "../../row";
|
|||||||
import { copyTextToClipboard } from "../../../helpers/clipboard";
|
import { copyTextToClipboard } from "../../../helpers/clipboard";
|
||||||
import { toast, toastNew } from "../../toast";
|
import { toast, toastNew } from "../../toast";
|
||||||
import { fastRaf } from "../../../helpers/schedulers";
|
import { fastRaf } from "../../../helpers/schedulers";
|
||||||
import { safeAssign } from "../../../helpers/object";
|
|
||||||
import { forEachReverse } from "../../../helpers/array";
|
|
||||||
import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
|
import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
|
||||||
import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl";
|
import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl";
|
||||||
import SwipeHandler from "../../swipeHandler";
|
import SwipeHandler from "../../swipeHandler";
|
||||||
@ -50,6 +48,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
|
|||||||
import replaceContent from "../../../helpers/dom/replaceContent";
|
import replaceContent from "../../../helpers/dom/replaceContent";
|
||||||
import appAvatarsManager from "../../../lib/appManagers/appAvatarsManager";
|
import appAvatarsManager from "../../../lib/appManagers/appAvatarsManager";
|
||||||
import generateVerifiedIcon from "../../generateVerifiedIcon";
|
import generateVerifiedIcon from "../../generateVerifiedIcon";
|
||||||
|
import ListLoader from "../../../helpers/listLoader";
|
||||||
|
import { forEachReverse } from "../../../helpers/array";
|
||||||
|
|
||||||
let setText = (text: string, row: Row) => {
|
let setText = (text: string, row: Row) => {
|
||||||
//fastRaf(() => {
|
//fastRaf(() => {
|
||||||
@ -60,115 +60,20 @@ let setText = (text: string, row: Row) => {
|
|||||||
|
|
||||||
const PARALLAX_SUPPORTED = !isFirefox;
|
const PARALLAX_SUPPORTED = !isFirefox;
|
||||||
|
|
||||||
type ListLoaderResult<T> = {count: number, items: any[]};
|
export function filterChatPhotosMessages(value: {
|
||||||
class ListLoader<T> {
|
count: number;
|
||||||
public current: T;
|
next_rate: number;
|
||||||
public previous: T[] = [];
|
offset_id_offset: number;
|
||||||
public next: T[] = [];
|
history: MyMessage[];
|
||||||
public count: number;
|
}) {
|
||||||
|
forEachReverse(value.history, (message, idx, arr) => {
|
||||||
public tempId = 0;
|
if(!((message as Message.messageService).action as MessageAction.messageActionChatEditPhoto).photo) {
|
||||||
public loadMore: (anchor: T, older: boolean) => Promise<ListLoaderResult<T>>;
|
arr.splice(idx, 1);
|
||||||
public processItem: (item: any) => false | T;
|
if(value.count !== undefined) {
|
||||||
public onJump: (item: T, older: boolean) => void;
|
--value.count;
|
||||||
public loadCount = 50;
|
|
||||||
public reverse = false; // reverse means next = higher msgid
|
|
||||||
|
|
||||||
public loadedAllUp = false;
|
|
||||||
public loadedAllDown = false;
|
|
||||||
public loadPromiseUp: Promise<void>;
|
|
||||||
public loadPromiseDown: Promise<void>;
|
|
||||||
|
|
||||||
constructor(options: {
|
|
||||||
loadMore: ListLoader<T>['loadMore'],
|
|
||||||
loadCount: ListLoader<T>['loadCount'],
|
|
||||||
processItem?: ListLoader<T>['processItem'],
|
|
||||||
onJump: ListLoader<T>['onJump'],
|
|
||||||
}) {
|
|
||||||
safeAssign(this, options);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
get index() {
|
|
||||||
return this.count !== undefined ? this.previous.length : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public go(length: number) {
|
|
||||||
let items: T[], item: T;
|
|
||||||
if(length > 0) {
|
|
||||||
items = this.next.splice(0, length);
|
|
||||||
item = items.pop();
|
|
||||||
if(!item) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previous.push(this.current, ...items);
|
|
||||||
} else {
|
|
||||||
items = this.previous.splice(this.previous.length + length, -length);
|
|
||||||
item = items.shift();
|
|
||||||
if(!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.next.unshift(...items, this.current);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
this.current = item;
|
|
||||||
this.onJump(item, length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public load(older: boolean) {
|
|
||||||
if(older && this.loadedAllDown) return Promise.resolve();
|
|
||||||
else if(!older && this.loadedAllUp) return Promise.resolve();
|
|
||||||
|
|
||||||
if(older && this.loadPromiseDown) return this.loadPromiseDown;
|
|
||||||
else if(!older && this.loadPromiseUp) return this.loadPromiseUp;
|
|
||||||
|
|
||||||
/* const loadCount = 50;
|
|
||||||
const backLimit = older ? 0 : loadCount; */
|
|
||||||
|
|
||||||
let anchor: T;
|
|
||||||
if(older) {
|
|
||||||
anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1];
|
|
||||||
} else {
|
|
||||||
anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = this.loadMore(anchor, older).then(result => {
|
|
||||||
if(result.items.length < this.loadCount) {
|
|
||||||
if(older) this.loadedAllDown = true;
|
|
||||||
else this.loadedAllUp = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.count === undefined) {
|
|
||||||
this.count = result.count || result.items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items);
|
|
||||||
method((item: any) => {
|
|
||||||
const processed = this.processItem ? this.processItem(item) : item;
|
|
||||||
|
|
||||||
if(!processed) return;
|
|
||||||
|
|
||||||
if(older) {
|
|
||||||
if(this.reverse) this.previous.unshift(processed);
|
|
||||||
else this.next.push(processed);
|
|
||||||
} else {
|
|
||||||
if(this.reverse) this.next.push(processed);
|
|
||||||
else this.previous.unshift(processed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, () => {}).then(() => {
|
|
||||||
if(older) this.loadPromiseDown = null;
|
|
||||||
else this.loadPromiseUp = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(older) this.loadPromiseDown = promise;
|
|
||||||
else this.loadPromiseUp = promise;
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PeerProfileAvatars {
|
class PeerProfileAvatars {
|
||||||
@ -361,15 +266,17 @@ class PeerProfileAvatars {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadCount = 50;
|
|
||||||
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string | Message.messageService>({
|
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string | Message.messageService>({
|
||||||
loadCount,
|
loadCount: 50,
|
||||||
loadMore: (anchor, older) => {
|
loadMore: (anchor, older, loadCount) => {
|
||||||
|
if(!older) return Promise.resolve({count: undefined, items: []});
|
||||||
|
|
||||||
if(peerId > 0) {
|
if(peerId > 0) {
|
||||||
return appPhotosManager.getUserPhotos(peerId, (anchor || listLoader.current) as any, loadCount).then(result => {
|
const maxId: string = (anchor || listLoader.current) as any;
|
||||||
|
return appPhotosManager.getUserPhotos(peerId, maxId, loadCount).then(value => {
|
||||||
return {
|
return {
|
||||||
count: result.count,
|
count: value.count,
|
||||||
items: result.photos
|
items: value.photos
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -391,6 +298,8 @@ class PeerProfileAvatars {
|
|||||||
return Promise.all(promises).then((result) => {
|
return Promise.all(promises).then((result) => {
|
||||||
const value = result.pop() as typeof result[1];
|
const value = result.pop() as typeof result[1];
|
||||||
|
|
||||||
|
filterChatPhotosMessages(value);
|
||||||
|
|
||||||
if(!listLoader.current) {
|
if(!listLoader.current) {
|
||||||
const chatFull = result[0];
|
const chatFull = result[0];
|
||||||
const message = value.history.findAndSplice(m => {
|
const message = value.history.findAndSplice(m => {
|
||||||
@ -429,6 +338,7 @@ class PeerProfileAvatars {
|
|||||||
|
|
||||||
this.processItem(listLoader.current);
|
this.processItem(listLoader.current);
|
||||||
|
|
||||||
|
// listLoader.loaded
|
||||||
listLoader.load(true);
|
listLoader.load(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +277,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
if(globalVideo.paused) {
|
if(globalVideo.paused) {
|
||||||
appMediaPlaybackController.setSearchContext(searchContext);
|
if(appMediaPlaybackController.setSearchContext(searchContext)) {
|
||||||
|
appMediaPlaybackController.setTargets({peerId: message.peerId, mid: message.mid});
|
||||||
|
}
|
||||||
|
|
||||||
globalVideo.play();
|
globalVideo.play();
|
||||||
} else {
|
} else {
|
||||||
globalVideo.pause();
|
globalVideo.pause();
|
||||||
@ -522,8 +525,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
|
|||||||
const uploading = message.pFlags.is_outgoing && message.media?.preloader;
|
const uploading = message.pFlags.is_outgoing && message.media?.preloader;
|
||||||
if(doc.type === 'audio' || doc.type === 'voice') {
|
if(doc.type === 'audio' || doc.type === 'voice') {
|
||||||
const audioElement = new AudioElement();
|
const audioElement = new AudioElement();
|
||||||
audioElement.setAttribute('message-id', '' + message.mid);
|
audioElement.dataset.mid = '' + message.mid;
|
||||||
audioElement.setAttribute('peer-id', '' + message.peerId);
|
audioElement.dataset.peerId = '' + message.peerId;
|
||||||
audioElement.withTime = withTime;
|
audioElement.withTime = withTime;
|
||||||
audioElement.message = message;
|
audioElement.message = message;
|
||||||
audioElement.noAutoDownload = noAutoDownload;
|
audioElement.noAutoDownload = noAutoDownload;
|
||||||
|
@ -1,53 +1,149 @@
|
|||||||
/*
|
/*
|
||||||
* https://github.com/morethanwords/tweb
|
* https://github.com/morethanwords/tweb
|
||||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Scrollable from "../components/scrollable";
|
import { forEachReverse } from "./array";
|
||||||
import { safeAssign } from "./object";
|
import { safeAssign } from "./object";
|
||||||
|
|
||||||
export default class ScrollableLoader {
|
export type ListLoaderOptions<T extends {}> = {
|
||||||
public loading = false;
|
loadMore: ListLoader<T>['loadMore'],
|
||||||
private scrollable: Scrollable;
|
loadCount?: ListLoader<T>['loadCount'],
|
||||||
private getPromise: () => Promise<any>;
|
loadWhenLeft?: ListLoader<T>['loadWhenLeft'],
|
||||||
private promise: Promise<any>;
|
processItem?: ListLoader<T>['processItem'],
|
||||||
private loaded = false;
|
onJump?: ListLoader<T>['onJump'],
|
||||||
|
onLoadedMore?: ListLoader<T>['onLoadedMore']
|
||||||
constructor(options: {
|
};
|
||||||
scrollable: ScrollableLoader['scrollable'],
|
|
||||||
getPromise: ScrollableLoader['getPromise']
|
export type ListLoaderResult<T extends {}> = {count: number, items: any[]};
|
||||||
}) {
|
export default class ListLoader<T extends {}> {
|
||||||
safeAssign(this, options);
|
public current: T;
|
||||||
|
public previous: T[] = [];
|
||||||
options.scrollable.onScrolledBottom = () => {
|
public next: T[] = [];
|
||||||
this.load();
|
public count: number;
|
||||||
};
|
public reverse = false; // reverse means next = higher msgid
|
||||||
}
|
|
||||||
|
protected loadMore: (anchor: T, older: boolean, loadCount: number) => Promise<ListLoaderResult<T>>;
|
||||||
public load() {
|
protected processItem: (item: any) => T;
|
||||||
if(this.loaded) {
|
protected loadCount = 50;
|
||||||
return Promise.resolve();
|
protected loadWhenLeft = 20;
|
||||||
}
|
|
||||||
|
public onJump: (item: T, older: boolean) => void;
|
||||||
if(this.loading) {
|
public onLoadedMore: () => void;
|
||||||
return this.promise;
|
|
||||||
}
|
protected loadedAllUp = false;
|
||||||
|
protected loadedAllDown = false;
|
||||||
this.loading = true;
|
protected loadPromiseUp: Promise<void>;
|
||||||
this.promise = this.getPromise().then(done => {
|
protected loadPromiseDown: Promise<void>;
|
||||||
this.loading = false;
|
|
||||||
this.promise = undefined;
|
constructor(options: ListLoaderOptions<T>) {
|
||||||
|
safeAssign(this, options);
|
||||||
if(done) {
|
}
|
||||||
this.loaded = true;
|
|
||||||
this.scrollable.onScrolledBottom = null;
|
public setTargets(previous: T[], next: T[], reverse: boolean) {
|
||||||
} else {
|
this.previous = previous;
|
||||||
this.scrollable.checkForTriggers();
|
this.next = next;
|
||||||
}
|
this.reverse = reverse;
|
||||||
}, () => {
|
}
|
||||||
this.promise = undefined;
|
|
||||||
this.loading = false;
|
public get index() {
|
||||||
});
|
return this.count !== undefined ? this.previous.length : -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public reset() {
|
||||||
|
this.current = undefined;
|
||||||
|
this.previous = [];
|
||||||
|
this.next = [];
|
||||||
|
this.loadedAllUp = this.loadedAllDown = false;
|
||||||
|
this.loadPromiseUp = this.loadPromiseDown = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public go(length: number) {
|
||||||
|
let items: T[], item: T;
|
||||||
|
if(length > 0) {
|
||||||
|
items = this.next.splice(0, length);
|
||||||
|
item = items.pop();
|
||||||
|
if(!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previous.push(this.current, ...items);
|
||||||
|
} else {
|
||||||
|
items = this.previous.splice(this.previous.length + length, -length);
|
||||||
|
item = items.shift();
|
||||||
|
if(!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.next.unshift(...items, this.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.next.length < this.loadWhenLeft) {
|
||||||
|
this.load(!this.reverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.previous.length < this.loadWhenLeft) {
|
||||||
|
this.load(this.reverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current = item;
|
||||||
|
this.onJump && this.onJump(item, length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// нет смысла делать проверку для reverse и loadMediaPromise
|
||||||
|
public load(older: boolean) {
|
||||||
|
if(older && this.loadedAllDown) return Promise.resolve();
|
||||||
|
else if(!older && this.loadedAllUp) return Promise.resolve();
|
||||||
|
|
||||||
|
if(older && this.loadPromiseDown) return this.loadPromiseDown;
|
||||||
|
else if(!older && this.loadPromiseUp) return this.loadPromiseUp;
|
||||||
|
|
||||||
|
let anchor: T;
|
||||||
|
if(older) {
|
||||||
|
anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1];
|
||||||
|
} else {
|
||||||
|
anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = this.loadMore(anchor, older, this.loadCount).then(result => {
|
||||||
|
if((older && this.loadPromiseDown !== promise) || (!older && this.loadPromiseUp !== promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result.items.length < this.loadCount) {
|
||||||
|
if(older) this.loadedAllDown = true;
|
||||||
|
else this.loadedAllUp = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.count === undefined) {
|
||||||
|
this.count = result.count || result.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items);
|
||||||
|
method((item: any) => {
|
||||||
|
const processed = this.processItem ? this.processItem(item) : item;
|
||||||
|
|
||||||
|
if(!processed) return;
|
||||||
|
|
||||||
|
if(older) {
|
||||||
|
if(this.reverse) this.previous.unshift(processed);
|
||||||
|
else this.next.push(processed);
|
||||||
|
} else {
|
||||||
|
if(this.reverse) this.next.push(processed);
|
||||||
|
else this.previous.unshift(processed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onLoadedMore && this.onLoadedMore();
|
||||||
|
}, () => {}).then(() => {
|
||||||
|
if(older) this.loadPromiseDown = null;
|
||||||
|
else this.loadPromiseUp = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(older) this.loadPromiseDown = promise;
|
||||||
|
else this.loadPromiseUp = promise;
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
53
src/helpers/scrollableLoader.ts
Normal file
53
src/helpers/scrollableLoader.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Scrollable from "../components/scrollable";
|
||||||
|
import { safeAssign } from "./object";
|
||||||
|
|
||||||
|
export default class ScrollableLoader {
|
||||||
|
public loading = false;
|
||||||
|
private scrollable: Scrollable;
|
||||||
|
private getPromise: () => Promise<any>;
|
||||||
|
private promise: Promise<any>;
|
||||||
|
private loaded = false;
|
||||||
|
|
||||||
|
constructor(options: {
|
||||||
|
scrollable: ScrollableLoader['scrollable'],
|
||||||
|
getPromise: ScrollableLoader['getPromise']
|
||||||
|
}) {
|
||||||
|
safeAssign(this, options);
|
||||||
|
|
||||||
|
options.scrollable.onScrolledBottom = () => {
|
||||||
|
this.load();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public load() {
|
||||||
|
if(this.loaded) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.loading) {
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
this.promise = this.getPromise().then(done => {
|
||||||
|
this.loading = false;
|
||||||
|
this.promise = undefined;
|
||||||
|
|
||||||
|
if(done) {
|
||||||
|
this.loaded = true;
|
||||||
|
this.scrollable.onScrolledBottom = null;
|
||||||
|
} else {
|
||||||
|
this.scrollable.checkForTriggers();
|
||||||
|
}
|
||||||
|
}, () => {
|
||||||
|
this.promise = undefined;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ export const ctx = typeof(window) !== 'undefined' ? window : self;
|
|||||||
// https://stackoverflow.com/a/58065241
|
// https://stackoverflow.com/a/58065241
|
||||||
export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||
|
export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||
|
||||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
||||||
!ctx.MSStream;
|
!(ctx as any).MSStream;
|
||||||
|
|
||||||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */;
|
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */;
|
||||||
export const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
export const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||||
|
@ -137,9 +137,17 @@ export class AppPhotosManager {
|
|||||||
photosResult.photos[idx] = this.savePhoto(photo, {type: 'profilePhoto', peerId: userId});
|
photosResult.photos[idx] = this.savePhoto(photo, {type: 'profilePhoto', peerId: userId});
|
||||||
return photo.id;
|
return photo.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! WARNING !
|
||||||
|
if(maxId !== '0' && maxId) {
|
||||||
|
const idx = photoIds.indexOf(maxId);
|
||||||
|
if(idx !== -1) {
|
||||||
|
photoIds.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count: (photosResult as PhotosPhotos.photosPhotosSlice).count || photosResult.photos.length,
|
count: (photosResult as PhotosPhotos.photosPhotosSlice).count || photoIds.length,
|
||||||
photos: photoIds
|
photos: photoIds
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -141,7 +141,7 @@ export default class IDBStorage<T extends Database<any>> {
|
|||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
this.log.error('error opening db', error.message)
|
this.log.error('error opening db', (error as Error).message);
|
||||||
this.storageIsAvailable = false;
|
this.storageIsAvailable = false;
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ export class ApiManager {
|
|||||||
|
|
||||||
networker = networkerFactory.getNetworker(dcId, auth.authKey, auth.authKeyId, auth.serverSalt, transport, options);
|
networker = networkerFactory.getNetworker(dcId, auth.authKey, auth.authKeyId, auth.serverSalt, transport, options);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
this.log('Get networker error', error, error.stack);
|
this.log('Get networker error', error, (error as Error).stack);
|
||||||
delete this.gettingNetworkers[getKey];
|
delete this.gettingNetworkers[getKey];
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ export class Authorizer {
|
|||||||
rsaKeysManager.prepare();
|
rsaKeysManager.prepare();
|
||||||
deserializer = await promise;
|
deserializer = await promise;
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
this.log.error('req_pq error', error.message);
|
this.log.error('req_pq error', (error as Error).message);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1262,7 +1262,7 @@ export default class MTPNetworker {
|
|||||||
try {
|
try {
|
||||||
result.body = deserializer.fetchObject('Object', field + '[body]');
|
result.body = deserializer.fetchObject('Object', field + '[body]');
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.log.error('parse error', e.message, e.stack);
|
this.log.error('parse error', (e as Error).message, (e as Error).stack);
|
||||||
result.body = {
|
result.body = {
|
||||||
_: 'parse_error',
|
_: 'parse_error',
|
||||||
error: e
|
error: e
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DcId } from '../types';
|
import type { DcId } from '../types';
|
||||||
|
import type { ApiError } from '../lib/mtproto/apiManager';
|
||||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||||
import Page from './page';
|
import Page from './page';
|
||||||
import serverTimeManager from '../lib/mtproto/serverTimeManager';
|
import serverTimeManager from '../lib/mtproto/serverTimeManager';
|
||||||
@ -200,10 +201,10 @@ let onFirstMount = async() => {
|
|||||||
await pause(diff > FETCH_INTERVAL ? 1e3 * FETCH_INTERVAL : 1e3 * diff | 0);
|
await pause(diff > FETCH_INTERVAL ? 1e3 * FETCH_INTERVAL : 1e3 * diff | 0);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
switch(err.type) {
|
switch((err as ApiError).type) {
|
||||||
case 'SESSION_PASSWORD_NEEDED':
|
case 'SESSION_PASSWORD_NEEDED':
|
||||||
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED');
|
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED');
|
||||||
err.handled = true;
|
(err as ApiError).handled = true;
|
||||||
import('./pagePassword').then(m => m.default.mount());
|
import('./pagePassword').then(m => m.default.mount());
|
||||||
stop = true;
|
stop = true;
|
||||||
cachedPromise = null;
|
cachedPromise = null;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user