Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1328 lines
46 KiB
1328 lines
46 KiB
![]()
4 years ago
|
import { deferredPromise } from "../helpers/cancellablePromise";
|
||
|
import mediaSizes from "../helpers/mediaSizes";
|
||
|
import { isTouchSupported } from "../helpers/touchSupport";
|
||
|
import { isSafari } from "../helpers/userAgent";
|
||
|
import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager";
|
||
|
import appImManager from "../lib/appManagers/appImManager";
|
||
|
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||
|
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||
|
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
||
|
import appProfileManager from "../lib/appManagers/appProfileManager";
|
||
|
import { logger } from "../lib/logger";
|
||
|
import VideoPlayer from "../lib/mediaPlayer";
|
||
|
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||
|
import $rootScope from "../lib/rootScope";
|
||
|
import { cancelEvent, fillPropertyValue, findUpClassName, generatePathData } from "../lib/utils";
|
||
|
import appMediaPlaybackController from "./appMediaPlaybackController";
|
||
|
import AvatarElement from "./avatar";
|
||
|
import ButtonIcon from "./buttonIcon";
|
||
|
import { ButtonMenuItemOptions } from "./buttonMenu";
|
||
|
import ButtonMenuToggle from "./buttonMenuToggle";
|
||
|
import { LazyLoadQueueBase } from "./lazyLoadQueue";
|
||
|
import { renderImageFromUrl } from "./misc";
|
||
|
import ProgressivePreloader from "./preloader";
|
||
|
import appSidebarRight, { AppSidebarRight } from "./sidebarRight";
|
||
|
import SwipeHandler from "./swipeHandler";
|
||
![]()
4 years ago
|
|
||
|
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
|
||
|
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
|
||
![]()
4 years ago
|
// TODO: видео в мобильной вёрстке, если показываются элементы управления: если свайпнуть в сторону, то элементы вернутся на место, т.е. прыгнут - это не ок, надо бы замаскировать
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const MEDIA_VIEWER_CLASSNAME = 'media-viewer';
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType extends string, TargetType extends object> {
|
||
|
public wholeDiv: HTMLElement;
|
||
|
protected overlaysDiv: HTMLElement;
|
||
|
protected author: {[k in 'container' | 'avatarEl' | 'nameEl' | 'date']: HTMLElement} = {} as any;
|
||
|
protected content: {[k in 'main' | 'container' | 'media' | 'mover' | ContentAdditionType]: HTMLElement} = {} as any;
|
||
|
public buttons: {[k in 'download' | 'close' | 'prev' | 'next' | 'mobile-close' | ButtonsAdditionType]: HTMLElement} = {} as any;
|
||
|
|
||
|
protected tempID = 0;
|
||
|
protected preloader: ProgressivePreloader = null;
|
||
|
protected preloaderStreamable: ProgressivePreloader = null;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected lastTarget: HTMLElement = null;
|
||
|
protected prevTargets: TargetType[] = [];
|
||
|
protected nextTargets: TargetType[] = [];
|
||
|
//protected targetContainer: HTMLElement = null;
|
||
|
//protected loadMore: () => void = null;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
public log: ReturnType<typeof logger>;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected peerID = 0;
|
||
|
protected loadMediaPromiseUp: Promise<void> = null;
|
||
|
protected loadMediaPromiseDown: Promise<void> = null;
|
||
|
protected loadedAllMediaUp = false;
|
||
|
protected loadedAllMediaDown = false;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected reverse = false; // reverse means next = higher msgid
|
||
|
protected needLoadMore = true;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected setMoverPromise: Promise<void>;
|
||
|
protected setMoverAnimationPromise: Promise<void>;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected lazyLoadQueue: LazyLoadQueueBase;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected highlightSwitchersTimeout: number;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected onDownloadClick: (e: MouseEvent) => void;
|
||
|
protected onPrevClick: (target: TargetType) => void;
|
||
|
protected onNextClick: (target: TargetType) => void;
|
||
|
protected loadMoreMedia: (older: boolean) => Promise<void>;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
constructor(topButtons: Array<keyof AppMediaViewerBase<ContentAdditionType, ButtonsAdditionType, TargetType>['buttons']>) {
|
||
|
this.log = logger('AMV');
|
||
|
this.preloader = new ProgressivePreloader();
|
||
|
this.preloaderStreamable = new ProgressivePreloader(undefined, false, true);
|
||
|
this.lazyLoadQueue = new LazyLoadQueueBase();
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.wholeDiv = document.createElement('div');
|
||
|
this.wholeDiv.classList.add(MEDIA_VIEWER_CLASSNAME + '-whole');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.overlaysDiv = document.createElement('div');
|
||
|
this.overlaysDiv.classList.add('overlays');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const mainDiv = document.createElement('div');
|
||
|
mainDiv.classList.add(MEDIA_VIEWER_CLASSNAME);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
// * author
|
||
|
this.author.container = document.createElement('div');
|
||
|
this.author.container.classList.add(MEDIA_VIEWER_CLASSNAME + '-author', 'no-select');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.author.avatarEl = new AvatarElement();
|
||
|
this.author.avatarEl.classList.add(MEDIA_VIEWER_CLASSNAME + '-userpic');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.author.nameEl = document.createElement('div');
|
||
|
this.author.nameEl.classList.add(MEDIA_VIEWER_CLASSNAME + '-name');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.author.date = document.createElement('div');
|
||
|
this.author.date.classList.add(MEDIA_VIEWER_CLASSNAME + '-date');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.author.container.append(this.author.avatarEl, this.author.nameEl, this.author.date);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
// * buttons
|
||
|
const buttonsDiv = document.createElement('div');
|
||
|
buttonsDiv.classList.add(MEDIA_VIEWER_CLASSNAME + '-buttons');
|
||
|
|
||
|
topButtons.concat(['download', 'close']).forEach(name => {
|
||
|
const button = ButtonIcon(name);
|
||
|
this.buttons[name] = button;
|
||
|
buttonsDiv.append(button);
|
||
|
});
|
||
|
|
||
|
// * content
|
||
|
this.content.main = document.createElement('div');
|
||
|
this.content.main.classList.add(MEDIA_VIEWER_CLASSNAME + '-content');
|
||
|
|
||
|
this.content.container = document.createElement('div');
|
||
|
this.content.container.classList.add(MEDIA_VIEWER_CLASSNAME + '-container');
|
||
|
|
||
|
this.content.media = document.createElement('div');
|
||
|
this.content.media.classList.add(MEDIA_VIEWER_CLASSNAME + '-media');
|
||
|
|
||
|
this.content.container.append(this.content.media);
|
||
|
|
||
|
this.content.main.append(this.content.container);
|
||
|
mainDiv.append(this.author.container, buttonsDiv, this.content.main);
|
||
|
this.overlaysDiv.append(mainDiv);
|
||
|
// * overlays end
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.buttons["mobile-close"] = ButtonIcon('close', {onlyMobile: true});
|
||
|
|
||
|
this.buttons.prev = document.createElement('div');
|
||
|
this.buttons.prev.className = `${MEDIA_VIEWER_CLASSNAME}-switcher ${MEDIA_VIEWER_CLASSNAME}-switcher-left`;
|
||
|
this.buttons.prev.innerHTML = `<span class="tgico-down ${MEDIA_VIEWER_CLASSNAME}-prev-button"></span>`;
|
||
|
|
||
|
this.buttons.next = document.createElement('div');
|
||
|
this.buttons.next.className = `${MEDIA_VIEWER_CLASSNAME}-switcher ${MEDIA_VIEWER_CLASSNAME}-switcher-right`;
|
||
|
this.buttons.next.innerHTML = `<span class="tgico-down ${MEDIA_VIEWER_CLASSNAME}-next-button"></span>`;
|
||
|
|
||
|
this.wholeDiv.append(this.overlaysDiv, this.buttons['mobile-close'], this.buttons.prev, this.buttons.next);
|
||
|
|
||
|
// * constructing html end
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.setNewMover();
|
||
|
}
|
||
|
|
||
|
protected setListeners() {
|
||
|
this.buttons.download.addEventListener('click', this.onDownloadClick);
|
||
![]()
4 years ago
|
[this.buttons.close, this.buttons["mobile-close"], this.preloaderStreamable.preloader].forEach(el => {
|
||
![]()
4 years ago
|
el.addEventListener('click', this.close.bind(this));
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.buttons.prev.addEventListener('click', (e) => {
|
||
|
cancelEvent(e);
|
||
![]()
4 years ago
|
if(this.setMoverPromise) return;
|
||
|
|
||
![]()
4 years ago
|
const target = this.prevTargets.pop();
|
||
![]()
4 years ago
|
if(target) {
|
||
![]()
4 years ago
|
this.onPrevClick(target);
|
||
![]()
4 years ago
|
} else {
|
||
|
this.buttons.prev.style.display = 'none';
|
||
|
}
|
||
|
});
|
||
|
|
||
![]()
4 years ago
|
this.buttons.next.addEventListener('click', (e) => {
|
||
|
cancelEvent(e);
|
||
![]()
4 years ago
|
if(this.setMoverPromise) return;
|
||
|
|
||
![]()
4 years ago
|
let target = this.nextTargets.shift();
|
||
![]()
4 years ago
|
if(target) {
|
||
![]()
4 years ago
|
this.onNextClick(target);
|
||
![]()
4 years ago
|
} else {
|
||
|
this.buttons.next.style.display = 'none';
|
||
|
}
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.wholeDiv.addEventListener('click', this.onClick);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(isTouchSupported) {
|
||
![]()
4 years ago
|
const swipeHandler = new SwipeHandler(this.wholeDiv, (xDiff, yDiff) => {
|
||
![]()
4 years ago
|
if(VideoPlayer.isFullScreen()) {
|
||
|
return;
|
||
|
}
|
||
![]()
4 years ago
|
//console.log(xDiff, yDiff);
|
||
|
|
||
|
const percents = Math.abs(xDiff) / appPhotosManager.windowW;
|
||
|
if(percents > .2 || xDiff > 125) {
|
||
|
//console.log('will swipe', xDiff);
|
||
|
|
||
|
if(xDiff < 0) {
|
||
|
this.buttons.prev.click();
|
||
|
} else {
|
||
|
this.buttons.next.click();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const percentsY = Math.abs(yDiff) / appPhotosManager.windowH;
|
||
|
if(percentsY > .2 || yDiff > 125) {
|
||
|
this.buttons.close.click();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected setBtnMenuToggle(buttons: ButtonMenuItemOptions[]) {
|
||
|
const btnMenuToggle = ButtonMenuToggle({onlyMobile: true}, 'bottom-left', buttons);
|
||
|
this.wholeDiv.append(btnMenuToggle);
|
||
|
}
|
||
|
|
||
|
public close(e?: MouseEvent) {
|
||
![]()
4 years ago
|
if(e) {
|
||
|
cancelEvent(e);
|
||
|
}
|
||
|
|
||
|
if(this.setMoverAnimationPromise) return;
|
||
|
|
||
|
this.peerID = 0;
|
||
|
this.lazyLoadQueue.clear();
|
||
|
|
||
|
const promise = this.setMoverToTarget(this.lastTarget, true).then(({onAnimationEnd}) => onAnimationEnd);
|
||
|
|
||
|
this.lastTarget = null;
|
||
|
this.prevTargets = [];
|
||
|
this.nextTargets = [];
|
||
|
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
|
||
|
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
|
||
|
this.setMoverPromise = null;
|
||
|
|
||
|
if(appSidebarRight.historyTabIDs.slice(-1)[0] == AppSidebarRight.SLIDERITEMSIDS.forward) {
|
||
|
promise.then(() => {
|
||
|
appSidebarRight.forwardTab.closeBtn.click();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
window.removeEventListener('keydown', this.onKeyDown);
|
||
|
|
||
![]()
4 years ago
|
promise.finally(() => {
|
||
|
this.wholeDiv.remove();
|
||
|
$rootScope.overlayIsActive = false;
|
||
![]()
4 years ago
|
});
|
||
|
|
||
![]()
4 years ago
|
return promise;
|
||
|
}
|
||
![]()
4 years ago
|
|
||
|
onClick = (e: MouseEvent) => {
|
||
|
if(this.setMoverAnimationPromise) return;
|
||
|
|
||
|
const target = e.target as HTMLElement;
|
||
|
if(target.tagName == 'A') return;
|
||
|
cancelEvent(e);
|
||
|
|
||
![]()
4 years ago
|
if(isTouchSupported) {
|
||
![]()
4 years ago
|
if(this.highlightSwitchersTimeout) {
|
||
|
clearTimeout(this.highlightSwitchersTimeout);
|
||
|
} else {
|
||
|
this.wholeDiv.classList.add('highlight-switchers');
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
this.highlightSwitchersTimeout = window.setTimeout(() => {
|
||
![]()
4 years ago
|
this.wholeDiv.classList.remove('highlight-switchers');
|
||
|
this.highlightSwitchersTimeout = 0;
|
||
|
}, 3e3);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
let mover: HTMLElement = null;
|
||
|
['media-viewer-mover', 'media-viewer-buttons', 'media-viewer-author'].find(s => {
|
||
|
try {
|
||
|
mover = findUpClassName(target, s);
|
||
|
if(mover) return true;
|
||
|
} catch(err) {return false;}
|
||
|
});
|
||
|
|
||
|
if(/* target == this.mediaViewerDiv */!mover || target.tagName == 'IMG' || target.tagName == 'image') {
|
||
|
this.buttons.close.click();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
onKeyDown = (e: KeyboardEvent) => {
|
||
![]()
4 years ago
|
//this.log('onKeyDown', e);
|
||
|
|
||
![]()
4 years ago
|
if(e.key == 'Escape') {
|
||
|
this.close();
|
||
|
} else if(e.key == 'ArrowRight') {
|
||
![]()
4 years ago
|
this.buttons.next.click();
|
||
|
} else if(e.key == 'ArrowLeft') {
|
||
|
this.buttons.prev.click();
|
||
|
}
|
||
![]()
4 years ago
|
};
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
|
||
![]()
4 years ago
|
const mover = this.content.mover;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(!target) {
|
||
![]()
4 years ago
|
target = this.content.media;
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
if(!closing) {
|
||
|
mover.innerHTML = '';
|
||
![]()
4 years ago
|
//mover.append(this.buttons.prev, this.buttons.next);
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
|
this.removeCenterFromMover(mover);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const wasActive = fromRight !== 0;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const delay = wasActive ? 350 : 200;
|
||
![]()
4 years ago
|
//let delay = wasActive ? 350 : 10000;
|
||
![]()
4 years ago
|
|
||
|
/* if(wasActive) {
|
||
|
this.moveTheMover(mover);
|
||
|
mover = this.setNewMover();
|
||
|
} */
|
||
|
|
||
![]()
4 years ago
|
this.log('setMoverToTarget', target, closing, wasActive, fromRight);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
let realParent: HTMLElement;
|
||
![]()
4 years ago
|
|
||
|
let rect: DOMRect;
|
||
|
if(target) {
|
||
![]()
4 years ago
|
if(target instanceof AvatarElement) {
|
||
|
realParent = target;
|
||
|
rect = target.getBoundingClientRect();
|
||
|
} else if(target instanceof SVGImageElement || target.parentElement instanceof SVGForeignObjectElement) {
|
||
![]()
4 years ago
|
realParent = findUpClassName(target, 'attachment');
|
||
|
rect = realParent.getBoundingClientRect();
|
||
|
} else {
|
||
![]()
4 years ago
|
realParent = target.parentElement as HTMLElement;
|
||
![]()
4 years ago
|
rect = target.getBoundingClientRect();
|
||
|
}
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
const containerRect = this.content.media.getBoundingClientRect();
|
||
![]()
4 years ago
|
|
||
|
let transform = '';
|
||
|
let left: number;
|
||
|
let top: number;
|
||
|
|
||
|
if(wasActive) {
|
||
|
left = fromRight === 1 ? appPhotosManager.windowW : -containerRect.width;
|
||
|
top = containerRect.top;
|
||
|
} else {
|
||
|
left = rect.left;
|
||
|
top = rect.top;
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
transform += `translate3d(${left}px,${top}px,0) `;
|
||
![]()
4 years ago
|
|
||
|
/* if(wasActive) {
|
||
|
left = fromRight === 1 ? appPhotosManager.windowW / 2 : -(containerRect.width + appPhotosManager.windowW / 2);
|
||
|
transform += `translate(${left}px,-50%) `;
|
||
|
} else {
|
||
|
left = rect.left - (appPhotosManager.windowW / 2);
|
||
|
top = rect.top - (appPhotosManager.windowH / 2);
|
||
|
transform += `translate(${left}px,${top}px) `;
|
||
|
} */
|
||
|
|
||
![]()
4 years ago
|
let aspecter: HTMLDivElement;
|
||
![]()
4 years ago
|
if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement || target.tagName == 'DIV') {
|
||
![]()
4 years ago
|
if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) {
|
||
|
aspecter = mover.firstElementChild as HTMLDivElement;
|
||
|
|
||
![]()
4 years ago
|
const player = aspecter.querySelector('.ckin__player');
|
||
![]()
4 years ago
|
if(player) {
|
||
![]()
4 years ago
|
const video = player.firstElementChild as HTMLVideoElement;
|
||
![]()
4 years ago
|
aspecter.append(video);
|
||
|
player.remove();
|
||
|
}
|
||
|
|
||
|
if(!aspecter.style.cssText) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
|
||
|
mover.classList.remove('active');
|
||
|
this.setFullAspect(aspecter, containerRect, rect);
|
||
|
void mover.offsetLeft; // reflow
|
||
|
mover.classList.add('active');
|
||
|
}
|
||
|
} else {
|
||
|
aspecter = document.createElement('div');
|
||
![]()
4 years ago
|
aspecter.classList.add('media-viewer-aspecter'/* , 'disable-hover' */);
|
||
![]()
4 years ago
|
mover.prepend(aspecter);
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale3d(${containerRect.width / rect.width}, ${containerRect.height / rect.height}, 1);`;
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
mover.style.width = containerRect.width + 'px';
|
||
|
mover.style.height = containerRect.height + 'px';
|
||
|
|
||
![]()
4 years ago
|
const scaleX = rect.width / containerRect.width;
|
||
|
const scaleY = rect.height / containerRect.height;
|
||
![]()
4 years ago
|
if(!wasActive) {
|
||
![]()
4 years ago
|
transform += `scale3d(${scaleX},${scaleY},1) `;
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
let borderRadius = window.getComputedStyle(realParent).getPropertyValue('border-radius');
|
||
![]()
4 years ago
|
const brSplitted = fillPropertyValue(borderRadius) as string[];
|
||
![]()
4 years ago
|
borderRadius = brSplitted.map(r => (parseInt(r) / scaleX) + 'px').join(' ');
|
||
|
if(!wasActive) {
|
||
![]()
4 years ago
|
mover.style.borderRadius = borderRadius;
|
||
|
}
|
||
![]()
4 years ago
|
//let borderRadius = '0px 0px 0px 0px';
|
||
![]()
4 years ago
|
|
||
|
mover.style.transform = transform;
|
||
|
|
||
![]()
4 years ago
|
/* if(wasActive) {
|
||
![]()
4 years ago
|
this.log('setMoverToTarget', mover.style.transform);
|
||
![]()
4 years ago
|
} */
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
let path: SVGPathElement;
|
||
![]()
4 years ago
|
const isOut = target.classList.contains('is-out');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const deferred = this.setMoverAnimationPromise = deferredPromise<void>();
|
||
|
const ret = {onAnimationEnd: deferred};
|
||
|
|
||
|
this.setMoverAnimationPromise.then(() => {
|
||
|
this.setMoverAnimationPromise = null;
|
||
|
});
|
||
|
|
||
![]()
4 years ago
|
if(!closing) {
|
||
![]()
4 years ago
|
let mediaElement: HTMLImageElement | HTMLVideoElement;
|
||
|
let src: string;
|
||
|
|
||
![]()
4 years ago
|
if(target.tagName == 'DIV' || target.tagName == 'AVATAR-ELEMENT') { // useContainerAsTarget
|
||
![]()
4 years ago
|
if(target.firstElementChild) {
|
||
|
mediaElement = new Image();
|
||
|
src = (target.firstElementChild as HTMLImageElement).src;
|
||
|
mover.append(mediaElement);
|
||
|
}
|
||
|
/* mediaElement = new Image();
|
||
|
src = target.style.backgroundImage.slice(5, -2); */
|
||
|
|
||
![]()
4 years ago
|
} else if(target instanceof HTMLImageElement) {
|
||
![]()
4 years ago
|
mediaElement = new Image();
|
||
|
src = target.src;
|
||
![]()
4 years ago
|
} else if(target instanceof HTMLVideoElement) {
|
||
![]()
4 years ago
|
const video = mediaElement = document.createElement('video');
|
||
![]()
4 years ago
|
video.src = target?.src;
|
||
![]()
4 years ago
|
} else if(target instanceof SVGSVGElement) {
|
||
![]()
4 years ago
|
const clipID = target.dataset.clipID;
|
||
|
const newClipID = clipID + '-mv';
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const {width, height} = containerRect;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const newSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||
![]()
4 years ago
|
newSvg.setAttributeNS(null, 'width', '' + width);
|
||
|
newSvg.setAttributeNS(null, 'height', '' + height);
|
||
|
|
||
![]()
4 years ago
|
// нижние два свойства для масштабирования
|
||
|
newSvg.setAttributeNS(null, 'viewBox', `0 0 ${width} ${height}`);
|
||
|
newSvg.setAttributeNS(null, 'preserveAspectRatio', 'xMidYMid meet');
|
||
|
|
||
![]()
4 years ago
|
newSvg.insertAdjacentHTML('beforeend', target.firstElementChild.outerHTML.replace(clipID, newClipID));
|
||
|
newSvg.insertAdjacentHTML('beforeend', target.lastElementChild.outerHTML.replace(clipID, newClipID));
|
||
|
|
||
|
// теперь надо выставить новую позицию для хвостика
|
||
![]()
4 years ago
|
const defs = newSvg.firstElementChild;
|
||
|
const use = defs.firstElementChild.firstElementChild as SVGUseElement;
|
||
![]()
4 years ago
|
if(use instanceof SVGUseElement) {
|
||
|
let transform = use.getAttributeNS(null, 'transform');
|
||
|
transform = transform.replace(/translate\((.+?), (.+?)\) scale\((.+?), (.+?)\)/, (match, x, y, sX, sY) => {
|
||
|
x = +x;
|
||
|
if(x != 2) {
|
||
|
x = width - (2 / scaleX);
|
||
|
} else {
|
||
|
x = 2 / scaleX;
|
||
|
}
|
||
|
|
||
|
y = height;
|
||
|
|
||
|
return `translate(${x}, ${y}) scale(${+sX / scaleX}, ${+sY / scaleY})`;
|
||
|
});
|
||
|
use.setAttributeNS(null, 'transform', transform);
|
||
|
|
||
|
// и новый RECT
|
||
|
path = defs.firstElementChild.lastElementChild as SVGPathElement;
|
||
|
|
||
|
// код ниже нужен только чтобы скрыть моргание до момента как сработает таймаут
|
||
|
let d: string;
|
||
![]()
4 years ago
|
const br: [number, number, number, number] = borderRadius.split(' ').map(v => parseInt(v)) as any;
|
||
![]()
4 years ago
|
if(isOut) d = generatePathData(0, 0, width - 9 / scaleX, height, ...br);
|
||
|
else d = generatePathData(9 / scaleX, 0, width - 9 / scaleX, height, ...br);
|
||
|
path.setAttributeNS(null, 'd', d);
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
const foreignObject = newSvg.lastElementChild;
|
||
![]()
4 years ago
|
foreignObject.setAttributeNS(null, 'width', '' + containerRect.width);
|
||
|
foreignObject.setAttributeNS(null, 'height', '' + containerRect.height);
|
||
![]()
4 years ago
|
|
||
|
mover.prepend(newSvg);
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
if(aspecter) {
|
||
|
aspecter.style.borderRadius = borderRadius;
|
||
![]()
4 years ago
|
|
||
|
if(mediaElement) {
|
||
|
aspecter.append(mediaElement);
|
||
|
}
|
||
![]()
4 years ago
|
}
|
||
|
|
||
|
mediaElement = mover.querySelector('video, img');
|
||
![]()
4 years ago
|
if(mediaElement instanceof HTMLImageElement) {
|
||
|
mediaElement.classList.add('thumbnail');
|
||
|
if(!aspecter) {
|
||
|
mediaElement.style.width = containerRect.width + 'px';
|
||
|
mediaElement.style.height = containerRect.height + 'px';
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(src) {
|
||
|
await new Promise((resolve, reject) => {
|
||
|
mediaElement.addEventListener('load', resolve);
|
||
|
|
||
|
if(src) {
|
||
|
mediaElement.src = src;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
![]()
4 years ago
|
}/* else if(mediaElement instanceof HTMLVideoElement && mediaElement.firstElementChild && ((mediaElement.firstElementChild as HTMLSourceElement).src || src)) {
|
||
![]()
4 years ago
|
await new Promise((resolve, reject) => {
|
||
|
mediaElement.addEventListener('loadeddata', resolve);
|
||
|
|
||
|
if(src) {
|
||
|
(mediaElement.firstElementChild as HTMLSourceElement).src = src;
|
||
|
}
|
||
|
});
|
||
![]()
4 years ago
|
} */
|
||
![]()
4 years ago
|
|
||
|
mover.style.display = '';
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
window.requestAnimationFrame(() => {
|
||
![]()
4 years ago
|
mover.classList.add(wasActive ? 'moving' : 'active');
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
} else {
|
||
![]()
4 years ago
|
/* if(mover.classList.contains('center')) {
|
||
|
mover.classList.remove('center');
|
||
|
void mover.offsetLeft; // reflow
|
||
|
} */
|
||
|
|
||
![]()
4 years ago
|
if(target instanceof SVGSVGElement) {
|
||
|
path = mover.querySelector('path');
|
||
|
|
||
|
if(path) {
|
||
|
this.sizeTailPath(path, containerRect, scaleX, delay, false, isOut, borderRadius);
|
||
|
}
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
if(target.classList.contains('media-viewer-media')) {
|
||
|
mover.classList.add('hiding');
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
setTimeout(() => {
|
||
![]()
4 years ago
|
this.wholeDiv.classList.remove('active');
|
||
![]()
4 years ago
|
}, 0);
|
||
|
|
||
|
setTimeout(() => {
|
||
|
mover.style.borderRadius = borderRadius;
|
||
|
|
||
|
if(mover.firstElementChild) {
|
||
|
(mover.firstElementChild as HTMLElement).style.borderRadius = borderRadius;
|
||
|
}
|
||
![]()
4 years ago
|
}, delay / 2);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
setTimeout(() => {
|
||
|
mover.innerHTML = '';
|
||
![]()
4 years ago
|
mover.classList.remove('moving', 'active', 'hiding');
|
||
![]()
4 years ago
|
mover.style.cssText = 'display: none;';
|
||
![]()
4 years ago
|
|
||
|
deferred.resolve();
|
||
![]()
4 years ago
|
}, delay);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
return ret;
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
//await new Promise((resolve) => setTimeout(resolve, 0));
|
||
![]()
4 years ago
|
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||
|
// * одного RAF'а недостаточно, иногда анимация с одним не срабатывает (преимущественно на мобильных)
|
||
|
await new Promise((resolve) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
// чтобы проверить установленную позицию - раскомментировать
|
||
![]()
4 years ago
|
//throw '';
|
||
|
|
||
![]()
4 years ago
|
//await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||
|
|
||
|
mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale3d(1,1,1)`;
|
||
![]()
4 years ago
|
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(aspecter) {
|
||
|
this.setFullAspect(aspecter, containerRect, rect);
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
//throw '';
|
||
|
|
||
![]()
4 years ago
|
setTimeout(() => {
|
||
|
mover.style.borderRadius = '';
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(mover.firstElementChild) {
|
||
|
(mover.firstElementChild as HTMLElement).style.borderRadius = '';
|
||
|
}
|
||
![]()
4 years ago
|
}, 0/* delay / 2 */);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
mover.dataset.timeout = '' + setTimeout(() => {
|
||
|
mover.classList.remove('moving');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
|
||
![]()
4 years ago
|
if(mover.querySelector('video') || true) {
|
||
![]()
4 years ago
|
mover.classList.remove('active');
|
||
|
aspecter.style.cssText = '';
|
||
|
void mover.offsetLeft; // reflow
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
//aspecter.classList.remove('disable-hover');
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
// эти строки нужны для установки центральной позиции, в случае ресайза это будет нужно
|
||
|
mover.classList.add('center', 'no-transition');
|
||
|
/* mover.style.left = mover.style.top = '50%';
|
||
|
mover.style.transform = 'translate(-50%, -50%)';
|
||
|
void mover.offsetLeft; // reflow */
|
||
|
|
||
|
// это уже нужно для будущих анимаций
|
||
![]()
4 years ago
|
mover.classList.add('active');
|
||
|
delete mover.dataset.timeout;
|
||
![]()
4 years ago
|
|
||
|
deferred.resolve();
|
||
![]()
4 years ago
|
}, delay);
|
||
|
|
||
|
if(path) {
|
||
|
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
|
||
|
}
|
||
![]()
4 years ago
|
|
||
|
return ret;
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
|
||
![]()
4 years ago
|
/* let media = aspecter.firstElementChild;
|
||
![]()
4 years ago
|
let proportion: number;
|
||
|
if(media instanceof HTMLImageElement) {
|
||
|
proportion = media.naturalWidth / media.naturalHeight;
|
||
|
} else if(media instanceof HTMLVideoElement) {
|
||
|
proportion = media.videoWidth / media.videoHeight;
|
||
![]()
4 years ago
|
} */
|
||
|
const proportion = containerRect.width / containerRect.height;
|
||
![]()
4 years ago
|
|
||
|
let {width, height} = rect;
|
||
![]()
4 years ago
|
/* if(proportion == 1) {
|
||
![]()
4 years ago
|
aspecter.style.cssText = '';
|
||
![]()
4 years ago
|
} else { */
|
||
![]()
4 years ago
|
if(proportion > 0) {
|
||
|
width = height * proportion;
|
||
|
} else {
|
||
|
height = width * proportion;
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
//this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`);
|
||
|
|
||
![]()
4 years ago
|
aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale3d(${containerRect.width / width}, ${containerRect.height / height}, 1);`;
|
||
![]()
4 years ago
|
//}
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
protected sizeTailPath(path: SVGPathElement, rect: DOMRect, scaleX: number, delay: number, upscale: boolean, isOut: boolean, borderRadius: string) {
|
||
![]()
4 years ago
|
const start = Date.now();
|
||
|
const {width, height} = rect;
|
||
![]()
4 years ago
|
delay = delay / 2;
|
||
|
|
||
![]()
4 years ago
|
const br = borderRadius.split(' ').map(v => parseInt(v));
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const step = () => {
|
||
|
const diff = Date.now() - start;
|
||
![]()
4 years ago
|
|
||
|
let progress = diff / delay;
|
||
|
if(progress > 1) progress = 1;
|
||
|
if(upscale) progress = 1 - progress;
|
||
|
|
||
![]()
4 years ago
|
const _br: [number, number, number, number] = br.map(v => v * progress) as any;
|
||
![]()
4 years ago
|
|
||
|
let d: string;
|
||
|
if(isOut) d = generatePathData(0, 0, width - (9 / scaleX * progress), height, ..._br);
|
||
|
else d = generatePathData(9 / scaleX * progress, 0, width/* width - (9 / scaleX * progress) */, height, ..._br);
|
||
|
path.setAttributeNS(null, 'd', d);
|
||
|
|
||
|
if(diff < delay) window.requestAnimationFrame(step);
|
||
|
};
|
||
|
|
||
|
//window.requestAnimationFrame(step);
|
||
|
step();
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
protected removeCenterFromMover(mover: HTMLElement) {
|
||
![]()
4 years ago
|
if(mover.classList.contains('center')) {
|
||
![]()
4 years ago
|
//const rect = mover.getBoundingClientRect();
|
||
![]()
4 years ago
|
const rect = this.content.media.getBoundingClientRect();
|
||
![]()
4 years ago
|
mover.style.transform = `translate3d(${rect.left}px,${rect.top}px,0)`;
|
||
![]()
4 years ago
|
mover.classList.remove('center');
|
||
|
void mover.offsetLeft; // reflow
|
||
|
mover.classList.remove('no-transition');
|
||
|
}
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
protected moveTheMover(mover: HTMLElement, toLeft = true) {
|
||
![]()
4 years ago
|
const windowW = appPhotosManager.windowW;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.removeCenterFromMover(mover);
|
||
|
|
||
![]()
4 years ago
|
//mover.classList.remove('active');
|
||
![]()
4 years ago
|
mover.classList.add('moving');
|
||
|
|
||
![]()
4 years ago
|
if(mover.dataset.timeout) { // и это тоже всё из-за скейла видео, так бы это не нужно было
|
||
|
clearTimeout(+mover.dataset.timeout);
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
const rect = mover.getBoundingClientRect();
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const newTransform = mover.style.transform.replace(/translate3d\((.+?),/, (match, p1) => {
|
||
![]()
4 years ago
|
const x = toLeft ? -rect.width : windowW;
|
||
|
//const x = toLeft ? -(rect.right + (rect.width / 2)) : windowW / 2;
|
||
![]()
4 years ago
|
|
||
|
return match.replace(p1, x + 'px');
|
||
|
});
|
||
|
|
||
![]()
4 years ago
|
////////this.log('set newTransform:', newTransform, mover.style.transform, toLeft);
|
||
![]()
4 years ago
|
mover.style.transform = newTransform;
|
||
|
|
||
|
setTimeout(() => {
|
||
|
mover.remove();
|
||
|
}, 350);
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
protected setNewMover() {
|
||
![]()
4 years ago
|
const newMover = document.createElement('div');
|
||
![]()
4 years ago
|
newMover.classList.add('media-viewer-mover');
|
||
|
|
||
![]()
4 years ago
|
if(this.content.mover) {
|
||
![]()
4 years ago
|
const oldMover = this.content.mover;
|
||
![]()
4 years ago
|
oldMover.parentElement.append(newMover);
|
||
|
} else {
|
||
|
this.wholeDiv.append(newMover);
|
||
|
}
|
||
![]()
4 years ago
|
|
||
|
return this.content.mover = newMover;
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
/* public isElementVisible(container: HTMLElement, target: HTMLElement) {
|
||
|
const rect = container.getBoundingClientRect();
|
||
|
const targetRect = target.getBoundingClientRect();
|
||
![]()
4 years ago
|
|
||
|
return targetRect.bottom > rect.top && targetRect.top < rect.bottom;
|
||
![]()
4 years ago
|
} */
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') {
|
||
![]()
4 years ago
|
//if(target instanceof SVGSVGElement) {
|
||
![]()
4 years ago
|
const el = target.querySelector(tagName) as HTMLElement;
|
||
![]()
4 years ago
|
renderImageFromUrl(el, url);
|
||
![]()
4 years ago
|
/* } else {
|
||
|
|
||
|
} */
|
||
|
}
|
||
![]()
4 years ago
|
|
||
|
protected setAuthorInfo(fromID: number, timestamp: number) {
|
||
|
const date = new Date(timestamp * 1000);
|
||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
|
|
||
|
const dateStr = months[date.getMonth()] + ' ' + date.getDate() + ' at '+ date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2);
|
||
|
this.author.date.innerText = dateStr;
|
||
|
|
||
|
const name = appPeersManager.getPeerTitle(fromID);
|
||
|
this.author.nameEl.innerHTML = name;
|
||
|
|
||
|
let oldAvatar = this.author.avatarEl;
|
||
|
this.author.avatarEl = (this.author.avatarEl.cloneNode() as AvatarElement);
|
||
|
this.author.avatarEl.setAttribute('peer', '' + fromID);
|
||
|
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
protected async _openMedia(media: any, fromID: number, fromRight: number, target?: HTMLElement, reverse = false,
|
||
|
prevTargets: TargetType[] = [], nextTargets: TargetType[] = [], needLoadMore = true) {
|
||
![]()
4 years ago
|
if(this.setMoverPromise) return this.setMoverPromise;
|
||
![]()
4 years ago
|
|
||
|
this.log('openMedia:', media, fromID);
|
||
|
|
||
|
this.setAuthorInfo(fromID, media.date);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const isVideo = (media as MyDocument).type == 'video' || (media as MyDocument).type == 'gif';
|
||
![]()
4 years ago
|
const isFirstOpen = !this.peerID;
|
||
![]()
4 years ago
|
|
||
|
if(isFirstOpen) {
|
||
|
this.peerID = $rootScope.selectedPeerID;
|
||
![]()
4 years ago
|
//this.targetContainer = targetContainer;
|
||
![]()
4 years ago
|
this.prevTargets = prevTargets;
|
||
|
this.nextTargets = nextTargets;
|
||
|
this.reverse = reverse;
|
||
![]()
4 years ago
|
this.needLoadMore = needLoadMore;
|
||
![]()
4 years ago
|
//this.loadMore = loadMore;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(appSidebarRight.historyTabIDs.slice(-1)[0] == AppSidebarRight.SLIDERITEMSIDS.forward) {
|
||
|
appSidebarRight.forwardTab.closeBtn.click();
|
||
![]()
4 years ago
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||
|
}
|
||
![]()
4 years ago
|
}
|
||
|
|
||
|
/* if(this.nextTargets.length < 10 && this.loadMore) {
|
||
|
this.loadMore();
|
||
|
} */
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
//if(prevTarget && (!prevTarget.parentElement || !this.isElementVisible(this.targetContainer, prevTarget))) prevTarget = null;
|
||
|
//if(nextTarget && (!nextTarget.parentElement || !this.isElementVisible(this.targetContainer, nextTarget))) nextTarget = null;
|
||
|
|
||
![]()
4 years ago
|
this.buttons.prev.classList.toggle('hide', !this.prevTargets.length);
|
||
|
this.buttons.next.classList.toggle('hide', !this.nextTargets.length);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const container = this.content.media;
|
||
![]()
4 years ago
|
const useContainerAsTarget = !target;
|
||
![]()
4 years ago
|
if(useContainerAsTarget) target = container;
|
||
|
|
||
![]()
4 years ago
|
this.lastTarget = target;
|
||
![]()
4 years ago
|
const tempID = ++this.tempID;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(this.needLoadMore) {
|
||
|
if(this.nextTargets.length < 20) {
|
||
|
this.loadMoreMedia(!this.reverse);
|
||
|
}
|
||
|
|
||
|
if(this.prevTargets.length < 20) {
|
||
|
this.loadMoreMedia(this.reverse);
|
||
|
}
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
|
if(container.firstElementChild) {
|
||
![]()
4 years ago
|
container.innerHTML = '';
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
// ok set
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const wasActive = fromRight !== 0;
|
||
![]()
4 years ago
|
if(wasActive) {
|
||
|
this.moveTheMover(this.content.mover, fromRight === 1);
|
||
|
this.setNewMover();
|
||
|
} else {
|
||
![]()
4 years ago
|
window.addEventListener('keydown', this.onKeyDown);
|
||
![]()
4 years ago
|
const mainColumns = this.pageEl.querySelector('#main-columns');
|
||
|
this.pageEl.insertBefore(this.wholeDiv, mainColumns);
|
||
|
void this.wholeDiv.offsetLeft; // reflow
|
||
![]()
4 years ago
|
this.wholeDiv.classList.add('active');
|
||
![]()
4 years ago
|
$rootScope.overlayIsActive = true;
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
////////this.log('wasActive:', wasActive);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const mover = this.content.mover;
|
||
|
|
||
|
//const maxWidth = appPhotosManager.windowW - 16;
|
||
![]()
4 years ago
|
const maxWidth = mediaSizes.isMobile ? this.pageEl.scrollWidth : this.pageEl.scrollWidth - 16;
|
||
![]()
4 years ago
|
const maxHeight = appPhotosManager.windowH - 100;
|
||
![]()
4 years ago
|
const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight);
|
||
![]()
4 years ago
|
|
||
|
// need after setAttachmentSize
|
||
![]()
4 years ago
|
/* if(useContainerAsTarget) {
|
||
![]()
4 years ago
|
target = target.querySelector('img, video') || target;
|
||
![]()
4 years ago
|
} */
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const preloader = media.supportsStreaming ? this.preloaderStreamable : this.preloader;
|
||
|
|
||
![]()
4 years ago
|
let setMoverPromise: Promise<void>;
|
||
![]()
4 years ago
|
if(isVideo) {
|
||
![]()
4 years ago
|
////////this.log('will wrap video', media, size);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
// потому что для safari нужно создать элемент из event'а
|
||
|
const video = document.createElement('video');
|
||
|
|
||
![]()
4 years ago
|
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
||
![]()
4 years ago
|
//return; // set and don't move
|
||
![]()
4 years ago
|
//if(wasActive) return;
|
||
![]()
4 years ago
|
//return;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||
![]()
4 years ago
|
//const video = mover.querySelector('video') || document.createElement('video');
|
||
|
|
||
|
const moverVideo = mover.querySelector('video');
|
||
|
if(moverVideo) {
|
||
|
moverVideo.remove();
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
//video.src = '';
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
video.setAttribute('playsinline', '');
|
||
![]()
4 years ago
|
|
||
|
if(isSafari) {
|
||
|
video.autoplay = true;
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
if(media.type == 'gif') {
|
||
![]()
4 years ago
|
video.muted = true;
|
||
![]()
4 years ago
|
video.autoplay = true;
|
||
![]()
4 years ago
|
video.loop = true;
|
||
![]()
4 years ago
|
}
|
||
|
|
||
![]()
4 years ago
|
if(!video.parentElement) {
|
||
|
div.append(video);
|
||
|
}
|
||
|
|
||
|
const canPlayThrough = new Promise((resolve) => {
|
||
![]()
4 years ago
|
video.addEventListener('canplay', resolve, {once: true});
|
||
![]()
4 years ago
|
});
|
||
|
|
||
![]()
4 years ago
|
const createPlayer = () => {
|
||
![]()
4 years ago
|
if(media.type != 'gif') {
|
||
|
video.dataset.ckin = 'default';
|
||
|
video.dataset.overlay = '1';
|
||
|
|
||
![]()
4 years ago
|
// fix for simultaneous play
|
||
![]()
4 years ago
|
appMediaPlaybackController.pause();
|
||
|
appMediaPlaybackController.willBePlayedMedia = null;
|
||
![]()
4 years ago
|
|
||
|
Promise.all([canPlayThrough, onAnimationEnd]).then(() => {
|
||
|
const player = new VideoPlayer(video, true, media.supportsStreaming);
|
||
|
/* div.append(video);
|
||
|
mover.append(player.wrapper); */
|
||
|
});
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
};
|
||
![]()
4 years ago
|
|
||
|
if(media.supportsStreaming) {
|
||
|
onAnimationEnd.then(() => {
|
||
|
if(video.readyState < video.HAVE_FUTURE_DATA) {
|
||
|
preloader.attach(mover, true);
|
||
|
}
|
||
|
|
||
|
/* canPlayThrough.then(() => {
|
||
|
preloader.detach();
|
||
|
}); */
|
||
|
});
|
||
|
|
||
|
const attachCanPlay = () => {
|
||
|
video.addEventListener('canplay', () => {
|
||
|
//this.log('video waited and progress loaded');
|
||
|
preloader.detach();
|
||
|
video.parentElement.classList.remove('is-buffering');
|
||
|
}, {once: true});
|
||
|
};
|
||
|
|
||
|
video.addEventListener('waiting', (e) => {
|
||
|
const loading = video.networkState === video.NETWORK_LOADING;
|
||
|
const isntEnoughData = video.readyState < video.HAVE_FUTURE_DATA;
|
||
|
|
||
|
//this.log('video waiting for progress', loading, isntEnoughData);
|
||
|
if(loading && isntEnoughData) {
|
||
|
attachCanPlay();
|
||
|
|
||
|
preloader.attach(mover, true);
|
||
|
|
||
|
// поставлю класс для плеера, чтобы убрать большую иконку пока прелоадер на месте
|
||
|
video.parentElement.classList.add('is-buffering');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
attachCanPlay();
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
//if(!video.src || media.url != video.src) {
|
||
![]()
4 years ago
|
const load = () => {
|
||
![]()
4 years ago
|
const promise = media.supportsStreaming ? Promise.resolve() : appDocsManager.downloadDocNew(media);
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(!media.supportsStreaming) {
|
||
|
onAnimationEnd.then(() => {
|
||
|
preloader.attach(mover, true, promise);
|
||
|
});
|
||
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
(promise as Promise<any>).then(async() => {
|
||
![]()
4 years ago
|
if(this.tempID != tempID) {
|
||
![]()
4 years ago
|
this.log.warn('media viewer changed video');
|
||
|
return;
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const url = media.url;
|
||
![]()
4 years ago
|
if(target instanceof SVGSVGElement/* && (video.parentElement || !isSafari) */) { // if video exists
|
||
|
//if(!video.parentElement) {
|
||
![]()
4 years ago
|
div.firstElementChild.lastElementChild.append(video);
|
||
![]()
4 years ago
|
//}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.updateMediaSource(mover, url, 'video');
|
||
![]()
4 years ago
|
} else {
|
||
![]()
4 years ago
|
renderImageFromUrl(video, url);
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
createPlayer();
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
return promise;
|
||
|
};
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.lazyLoadQueue.unshift({load});
|
||
![]()
4 years ago
|
//} else createPlayer();
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
} else {
|
||
![]()
4 years ago
|
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
||
![]()
4 years ago
|
//return; // set and don't move
|
||
![]()
4 years ago
|
//if(wasActive) return;
|
||
![]()
4 years ago
|
//return;
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
const load = () => {
|
||
|
const cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||
![]()
4 years ago
|
onAnimationEnd.then(() => {
|
||
|
this.preloader.attach(mover, true, cancellablePromise);
|
||
|
});
|
||
![]()
4 years ago
|
cancellablePromise.then(() => {
|
||
![]()
4 years ago
|
if(this.tempID != tempID) {
|
||
![]()
4 years ago
|
this.log.warn('media viewer changed photo');
|
||
|
return;
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
|
///////this.log('indochina', blob);
|
||
|
|
||
![]()
4 years ago
|
const url = media.url;
|
||
![]()
4 years ago
|
if(target instanceof SVGSVGElement) {
|
||
|
this.updateMediaSource(target, url, 'img');
|
||
|
this.updateMediaSource(mover, url, 'img');
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
if(mediaSizes.isMobile) {
|
||
|
const imgs = mover.querySelectorAll('img');
|
||
|
if(imgs && imgs.length) {
|
||
|
imgs.forEach(img => {
|
||
|
img.classList.remove('thumbnail'); // может здесь это вообще не нужно
|
||
|
});
|
||
|
}
|
||
|
}
|
||
![]()
4 years ago
|
} else {
|
||
![]()
4 years ago
|
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||
![]()
4 years ago
|
let image = div.firstElementChild as HTMLImageElement;
|
||
|
if(!image || image.tagName != 'IMG') {
|
||
|
image = new Image();
|
||
|
}
|
||
|
|
||
|
//this.log('will renderImageFromUrl:', image, div, target);
|
||
|
|
||
![]()
4 years ago
|
renderImageFromUrl(image, url, () => {
|
||
![]()
4 years ago
|
if(mediaSizes.isMobile) {
|
||
|
image.classList.remove('thumbnail'); // может здесь это вообще не нужно
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
div.append(image);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.preloader.detach();
|
||
|
}).catch(err => {
|
||
|
this.log.error(err);
|
||
|
});
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
return cancellablePromise;
|
||
|
};
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
this.lazyLoadQueue.unshift({load});
|
||
![]()
4 years ago
|
});
|
||
![]()
4 years ago
|
}
|
||
![]()
4 years ago
|
|
||
![]()
4 years ago
|
return this.setMoverPromise = setMoverPromise.catch(() => {
|
||
|
this.setMoverAnimationPromise = null;
|
||
|
}).finally(() => {
|
||
![]()
4 years ago
|
this.setMoverPromise = null;
|
||
|
});
|
||
![]()
4 years ago
|
}
|
||
|
}
|
||
|
|
||
![]()
4 years ago
|
type AppMediaViewerTargetType = {
|
||
|
element: HTMLElement,
|
||
|
mid: number
|
||
|
};
|
||
|
export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
|
||
|
public currentMessageID = 0;
|
||
|
|
||
|
constructor() {
|
||
|
super(['delete', 'forward']);
|
||
|
|
||
|
const stub = document.createElement('div');
|
||
|
stub.classList.add(MEDIA_VIEWER_CLASSNAME + '-stub');
|
||
|
this.content.main.prepend(stub);
|
||
|
|
||
|
this.content.caption = document.createElement('div');
|
||
|
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption');
|
||
|
this.content.main.append(this.content.caption);
|
||
|
|
||
|
this.setBtnMenuToggle([{
|
||
|
icon: 'forward',
|
||
|
text: 'Forward',
|
||
|
onClick: this.onForwardClick
|
||
|
}, {
|
||
|
icon: 'download',
|
||
|
text: 'Download',
|
||
|
onClick: this.onDownloadClick
|
||
|
}, {
|
||
|
icon: 'delete',
|
||
|
text: 'Delete',
|
||
|
onClick: () => {}
|
||
|
}]);
|
||
|
|
||
|
// * constructing html end
|
||
|
|
||
|
this.setListeners();
|
||
|
}
|
||
|
|
||
|
protected setListeners() {
|
||
|
super.setListeners();
|
||
|
this.buttons.forward.addEventListener('click', this.onForwardClick);
|
||
|
this.author.container.addEventListener('click', this.onAuthorClick);
|
||
|
}
|
||
|
|
||
|
public close(e?: MouseEvent) {
|
||
|
const good = !this.setMoverAnimationPromise;
|
||
|
const promise = super.close(e);
|
||
|
|
||
|
if(good) { // clear
|
||
|
this.currentMessageID = 0;
|
||
|
}
|
||
|
|
||
|
return promise;
|
||
|
}
|
||
|
|
||
|
onPrevClick = (target: AppMediaViewerTargetType) => {
|
||
|
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageID});
|
||
|
this.openMedia(appMessagesManager.getMessage(target.mid), target.element);
|
||
|
};
|
||
|
|
||
|
onNextClick = (target: AppMediaViewerTargetType) => {
|
||
|
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageID});
|
||
|
this.openMedia(appMessagesManager.getMessage(target.mid), target.element);
|
||
|
};
|
||
|
|
||
|
onForwardClick = (e: MouseEvent) => {
|
||
|
if(this.currentMessageID) {
|
||
|
appSidebarRight.forwardTab.open([this.currentMessageID]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
onAuthorClick = (e: MouseEvent) => {
|
||
|
if(this.currentMessageID) {
|
||
|
const mid = this.currentMessageID;
|
||
|
this.close(e).then(() => {
|
||
|
const message = appMessagesManager.getMessage(mid);
|
||
|
appImManager.setPeer(message.peerID, mid);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
onDownloadClick = (e: MouseEvent) => {
|
||
|
const message = appMessagesManager.getMessage(this.currentMessageID);
|
||
|
if(message.media.photo) {
|
||
|
appPhotosManager.savePhotoFile(message.media.photo);
|
||
|
} else {
|
||
|
let document: MyDocument = null;
|
||
|
|
||
|
if(message.media.webpage) document = message.media.webpage.document;
|
||
|
else document = message.media.document;
|
||
|
|
||
|
if(document) {
|
||
|
//console.log('will save document:', document);
|
||
|
appDocsManager.saveDocFile(document);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// нет смысла делать проверку для reverse и loadMediaPromise
|
||
|
protected loadMoreMedia = (older = true) => {
|
||
|
//if(!older && this.reverse) return;
|
||
|
|
||
|
if(older && this.loadedAllMediaDown) return;
|
||
|
else if(!older && this.loadedAllMediaUp) return;
|
||
|
|
||
|
if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown;
|
||
|
else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp;
|
||
|
|
||
|
const loadCount = 50;
|
||
|
const backLimit = older ? 0 : loadCount;
|
||
|
let maxID = this.currentMessageID;
|
||
|
|
||
|
let anchor: {element: HTMLElement, mid: number};
|
||
|
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];
|
||
|
}
|
||
|
|
||
|
if(anchor) maxID = anchor.mid;
|
||
|
if(!older) maxID += 1;
|
||
|
|
||
|
const peerID = this.peerID;
|
||
|
|
||
|
const promise = appMessagesManager.getSearch(peerID, '',
|
||
|
{_: 'inputMessagesFilterPhotoVideo'}, maxID, loadCount/* older ? loadCount : 0 */, 0, backLimit).then(value => {
|
||
|
if(this.peerID != peerID) {
|
||
|
this.log.warn('peer changed');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.log('loaded more media by maxID:', maxID, value, older, this.reverse);
|
||
|
|
||
|
if(value.history.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 = older ? value.history.forEach : value.history.forEachReverse;
|
||
|
method.call(value.history, mid => {
|
||
|
const message = appMessagesManager.getMessage(mid);
|
||
|
const media = message.media;
|
||
|
|
||
|
if(!media || !(media.photo || media.document || (media.webpage && media.webpage.document))) return;
|
||
|
if(media._ == 'document' && media.type != 'video') return;
|
||
|
|
||
|
const t = {element: null as HTMLElement, mid: mid};
|
||
|
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.buttons.prev.style.display = this.prevTargets.length ? '' : 'none';
|
||
|
this.buttons.next.style.display = this.nextTargets.length ? '' : 'none';
|
||
|
}, () => {}).then(() => {
|
||
|
if(older) this.loadMediaPromiseDown = null;
|
||
|
else this.loadMediaPromiseUp = null;
|
||
|
});
|
||
|
|
||
|
if(older) this.loadMediaPromiseDown = promise;
|
||
|
else this.loadMediaPromiseUp = promise;
|
||
|
|
||
|
return promise;
|
||
|
};
|
||
|
|
||
|
private setCaption(message: any) {
|
||
|
const caption = message.message;
|
||
|
if(caption) {
|
||
|
this.content.caption.innerHTML = RichTextProcessor.wrapRichText(caption, {
|
||
|
entities: message.totalEntities
|
||
|
});
|
||
|
} else {
|
||
|
this.content.caption.innerHTML = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async openMedia(message: any, target?: HTMLElement, reverse = false,
|
||
|
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) {
|
||
|
if(this.setMoverPromise) return this.setMoverPromise;
|
||
|
|
||
|
const fromID = message.fromID;
|
||
|
const media = message.media.photo || message.media.document || message.media.webpage.document || message.media.webpage.photo;
|
||
|
|
||
|
const isFirstOpen = !this.peerID;
|
||
|
let fromRight = 0;
|
||
|
if(!isFirstOpen) {
|
||
|
//if(this.lastTarget === prevTarget) {
|
||
|
if(this.reverse) fromRight = this.currentMessageID < message.mid ? 1 : -1;
|
||
|
else fromRight = this.currentMessageID > message.mid ? 1 : -1;
|
||
|
} else {
|
||
|
this.reverse = reverse;
|
||
|
}
|
||
|
|
||
|
this.currentMessageID = message.mid;
|
||
|
const promise = super._openMedia(media, fromID, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore);
|
||
|
this.setCaption(message);
|
||
|
|
||
|
return promise;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', {}> {
|
||
|
constructor() {
|
||
|
super(['delete']);
|
||
|
|
||
|
this.setBtnMenuToggle([{
|
||
|
icon: 'download',
|
||
|
text: 'Download',
|
||
|
onClick: this.onDownloadClick
|
||
|
}, {
|
||
|
icon: 'delete',
|
||
|
text: 'Delete',
|
||
|
onClick: () => {}
|
||
|
}]);
|
||
|
|
||
|
// * constructing html end
|
||
|
|
||
|
this.setListeners();
|
||
|
}
|
||
|
|
||
|
onDownloadClick = (e: MouseEvent) => {
|
||
|
|
||
|
};
|
||
|
|
||
|
protected loadMoreMedia = (older = true) => {
|
||
|
return Promise.resolve();
|
||
|
};
|
||
|
|
||
|
public async openMedia(peerID: number, target?: HTMLElement, reverse = false,
|
||
|
prevTargets: AppMediaViewerAvatar['prevTargets'] = [], nextTargets: AppMediaViewerAvatar['prevTargets'] = [], needLoadMore = true) {
|
||
|
if(this.setMoverPromise) return this.setMoverPromise;
|
||
|
|
||
|
return appProfileManager.getFullPhoto(peerID).then(photo => {
|
||
|
const fromID = peerID;
|
||
|
|
||
|
const isFirstOpen = !this.peerID;
|
||
|
let fromRight = 0;
|
||
|
if(!isFirstOpen) {
|
||
|
//if(this.lastTarget === prevTarget) {
|
||
|
/* if(this.reverse) fromRight = this.currentMessageID < message.mid ? 1 : -1;
|
||
|
else fromRight = this.currentMessageID > message.mid ? 1 : -1; */
|
||
|
fromRight = 1;
|
||
|
} else {
|
||
|
this.reverse = reverse;
|
||
|
}
|
||
|
|
||
|
const promise = super._openMedia(photo, fromID, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore);
|
||
|
});
|
||
|
}
|
||
|
}
|