2020-02-06 22:43:07 +07:00
import appPeersManager from "./appPeersManager" ;
import appPhotosManager from "./appPhotosManager" ;
import appMessagesManager from "./appMessagesManager" ;
import { RichTextProcessor } from "../richtextprocessor" ;
2020-06-21 15:25:17 +03:00
import { logger } from "../logger" ;
2020-02-11 22:35:57 +07:00
import ProgressivePreloader from "../../components/preloader" ;
2020-05-30 09:44:54 +03:00
import { findUpClassName , $rootScope , generatePathData , fillPropertyValue , cancelEvent } from "../utils" ;
2020-09-17 22:33:23 +03:00
import appDocsManager , { MyDocument } from "./appDocsManager" ;
2020-04-25 04:17:50 +03:00
import VideoPlayer from "../mediaPlayer" ;
2020-05-30 09:44:54 +03:00
import { renderImageFromUrl , parseMenuButtonsTo } from "../../components/misc" ;
2020-05-13 18:26:40 +03:00
import AvatarElement from "../../components/avatar" ;
2020-09-23 23:29:53 +03:00
import { LazyLoadQueueBase } from "../../components/lazyLoadQueue" ;
import { touchSupport } from "../config" ;
2020-08-29 14:45:37 +03:00
import appMediaPlaybackController from "../../components/appMediaPlaybackController" ;
2020-09-23 23:29:53 +03:00
import { deferredPromise } from "../../helpers/cancellablePromise" ;
import mediaSizes from "../../helpers/mediaSizes" ;
import { isSafari } from "../../helpers/userAgent" ;
2020-09-24 02:37:22 +03:00
import appSidebarRight , { AppSidebarRight } from "./appSidebarRight" ;
2020-08-27 21:25:47 +03:00
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
2020-09-01 15:53:46 +03:00
// TODO: видео в мобильной вёрстке, если показываются элементы управления: если свайпнуть в сторону, то элементы вернутся на место, т.е . прыгнут - это не ок, надо бы замаскировать
2020-02-06 22:43:07 +07:00
2020-08-28 22:53:10 +03:00
class SwipeHandler {
private xDown : number ;
private yDown : number ;
constructor ( element : HTMLElement , private onSwipe : ( xDiff : number , yDiff : number ) = > boolean ) {
element . addEventListener ( 'touchstart' , this . handleTouchStart , false ) ;
element . addEventListener ( 'touchmove' , this . handleTouchMove , false ) ;
}
handleTouchStart = ( evt : TouchEvent ) = > {
2020-09-01 19:44:53 +03:00
// * Fix for seek input
if ( ( evt . target as HTMLElement ) . tagName == 'INPUT' ) {
this . xDown = this . yDown = null ;
return ;
}
2020-08-28 22:53:10 +03:00
const firstTouch = evt . touches [ 0 ] ;
this . xDown = firstTouch . clientX ;
this . yDown = firstTouch . clientY ;
} ;
handleTouchMove = ( evt : TouchEvent ) = > {
if ( this . xDown == null || this . yDown == null ) {
return ;
}
const xUp = evt . touches [ 0 ] . clientX ;
const yUp = evt . touches [ 0 ] . clientY ;
const xDiff = this . xDown - xUp ;
const yDiff = this . yDown - yUp ;
// if(Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
// if(xDiff > 0) { /* left swipe */
// } else { /* right swipe */
// }
// } else {
// if(yDiff > 0) { /* up swipe */
// } else { /* down swipe */
// }
// }
/* reset values */
if ( this . onSwipe ( xDiff , yDiff ) ) {
this . xDown = null ;
this . yDown = null ;
}
} ;
}
2020-02-07 13:38:55 +07:00
export class AppMediaViewer {
2020-05-30 09:44:54 +03:00
public wholeDiv = document . querySelector ( '.media-viewer-whole' ) as HTMLDivElement ;
private overlaysDiv = this . wholeDiv . firstElementChild as HTMLDivElement ;
2020-02-06 22:43:07 +07:00
private author = {
2020-05-13 18:26:40 +03:00
avatarEl : this.overlaysDiv.querySelector ( '.media-viewer-userpic' ) as AvatarElement ,
2020-02-06 22:43:07 +07:00
nameEl : this.overlaysDiv.querySelector ( '.media-viewer-name' ) as HTMLDivElement ,
date : this.overlaysDiv.querySelector ( '.media-viewer-date' ) as HTMLDivElement
} ;
2020-06-05 19:01:06 +03:00
public buttons : { [ k in 'delete' | 'forward' | 'download' | 'close' | 'prev' | 'next' |
'menu-delete' | 'menu-forward' | 'menu-download' | 'mobile-close' ] : HTMLElement } = { } as any ;
2020-05-30 09:44:54 +03:00
private content : { [ k in 'container' | 'caption' | 'mover' ] : HTMLDivElement } = {
container : this.overlaysDiv.querySelector ( '.media-viewer-media' ) ,
caption : this.overlaysDiv.querySelector ( '.media-viewer-caption' ) ,
mover : null
2020-02-06 22:43:07 +07:00
} ;
2020-02-07 13:38:55 +07:00
public currentMessageID = 0 ;
2020-02-06 22:43:07 +07:00
private preloader : ProgressivePreloader = null ;
2020-08-27 21:25:47 +03:00
private preloaderStreamable : ProgressivePreloader = null ;
2020-02-07 13:38:55 +07:00
2020-02-15 12:54:55 +07:00
private lastTarget : HTMLElement = null ;
2020-04-08 18:46:43 +03:00
private prevTargets : {
element : HTMLElement ,
mid : number
} [ ] = [ ] ;
private nextTargets : AppMediaViewer [ 'prevTargets' ] = [ ] ;
2020-06-05 19:01:06 +03:00
//private targetContainer: HTMLElement = null;
//private loadMore: () => void = null;
2020-02-15 12:54:55 +07:00
2020-02-07 13:38:55 +07:00
public log : ReturnType < typeof logger > ;
2020-04-08 18:46:43 +03:00
private peerID = 0 ;
private loadMediaPromiseUp : Promise < void > = null ;
private loadMediaPromiseDown : Promise < void > = null ;
private loadedAllMediaUp = false ;
private loadedAllMediaDown = false ;
private reverse = false ; // reverse means next = higher msgid
2020-05-02 00:28:40 +03:00
private needLoadMore = true ;
2020-05-23 08:31:18 +03:00
private pageEl = document . getElementById ( 'page-chats' ) as HTMLDivElement ;
2020-05-27 09:21:16 +03:00
private setMoverPromise : Promise < void > ;
2020-08-27 21:25:47 +03:00
private setMoverAnimationPromise : Promise < void > ;
2020-05-27 09:21:16 +03:00
2020-09-20 01:38:00 +03:00
private lazyLoadQueue : LazyLoadQueueBase ;
2020-08-28 22:53:10 +03:00
private highlightSwitchersTimeout : number ;
2020-02-06 22:43:07 +07:00
constructor ( ) {
2020-02-07 13:38:55 +07:00
this . log = logger ( 'AMV' ) ;
2020-02-06 22:43:07 +07:00
this . preloader = new ProgressivePreloader ( ) ;
2020-08-27 21:25:47 +03:00
2020-08-28 15:37:15 +03:00
this . preloaderStreamable = new ProgressivePreloader ( undefined , false , true ) ;
2020-08-27 21:25:47 +03:00
2020-09-20 01:38:00 +03:00
this . lazyLoadQueue = new LazyLoadQueueBase ( ) ;
2020-02-15 18:14:58 +07:00
2020-05-30 09:44:54 +03:00
parseMenuButtonsTo ( this . buttons , this . wholeDiv . querySelectorAll ( ` [class*='menu'] ` ) as NodeListOf < HTMLElement > ) ;
2020-06-05 19:01:06 +03:00
const close = ( e : MouseEvent ) = > {
2020-05-30 09:44:54 +03:00
cancelEvent ( e ) ;
2020-02-15 12:54:55 +07:00
//this.overlaysDiv.classList.remove('active');
2020-02-06 22:43:07 +07:00
this . content . container . innerHTML = '' ;
2020-06-05 19:01:06 +03:00
/ * i f ( t h i s . c o n t e n t . c o n t a i n e r . f i r s t E l e m e n t C h i l d ) {
2020-03-03 00:15:11 +07:00
URL . revokeObjectURL ( ( this . content . container . firstElementChild as HTMLImageElement ) . src ) ;
2020-06-05 19:01:06 +03:00
} * /
2020-03-03 00:15:11 +07:00
2020-04-08 18:46:43 +03:00
this . peerID = 0 ;
2020-02-07 13:38:55 +07:00
this . currentMessageID = 0 ;
2020-05-27 09:21:16 +03:00
this . lazyLoadQueue . clear ( ) ;
2020-02-15 12:54:55 +07:00
this . setMoverToTarget ( this . lastTarget , true ) ;
2020-02-15 18:14:58 +07:00
this . lastTarget = null ;
2020-04-08 18:46:43 +03:00
this . prevTargets = [ ] ;
this . nextTargets = [ ] ;
this . loadedAllMediaUp = this . loadedAllMediaDown = false ;
this . loadMediaPromiseUp = this . loadMediaPromiseDown = null ;
2020-06-05 19:01:06 +03:00
this . setMoverPromise = null ;
2020-02-15 18:14:58 +07:00
2020-09-24 02:37:22 +03:00
if ( appSidebarRight . historyTabIDs . slice ( - 1 ) [ 0 ] == AppSidebarRight . SLIDERITEMSIDS . forward ) {
2020-06-05 19:01:06 +03:00
setTimeout ( ( ) = > {
2020-09-24 02:37:22 +03:00
appSidebarRight . forwardTab . closeBtn . click ( ) ;
2020-06-05 19:01:06 +03:00
} , 200 ) ;
}
2020-05-30 09:44:54 +03:00
2020-08-27 21:25:47 +03:00
window . removeEventListener ( 'keydown' , this . onKeyDown ) ;
2020-06-05 19:01:06 +03:00
} ;
2020-08-27 21:25:47 +03:00
[ this . buttons . close , this . buttons [ "mobile-close" ] , this . preloaderStreamable . preloader ] . forEach ( el = > {
2020-06-05 19:01:06 +03:00
el . addEventListener ( 'click' , close ) ;
2020-02-06 22:43:07 +07:00
} ) ;
2020-05-30 09:44:54 +03:00
this . buttons . prev . addEventListener ( 'click' , ( e ) = > {
cancelEvent ( e ) ;
2020-05-27 09:21:16 +03:00
if ( this . setMoverPromise ) return ;
2020-04-08 18:46:43 +03:00
let target = this . prevTargets . pop ( ) ;
2020-02-15 12:54:55 +07:00
if ( target ) {
2020-04-08 18:46:43 +03:00
this . nextTargets . unshift ( { element : this.lastTarget , mid : this.currentMessageID } ) ;
this . openMedia ( appMessagesManager . getMessage ( target . mid ) , target . element ) ;
2020-02-15 12:54:55 +07:00
} else {
this . buttons . prev . style . display = 'none' ;
}
} ) ;
2020-05-30 09:44:54 +03:00
this . buttons . next . addEventListener ( 'click' , ( e ) = > {
cancelEvent ( e ) ;
2020-05-27 09:21:16 +03:00
if ( this . setMoverPromise ) return ;
2020-04-08 18:46:43 +03:00
let target = this . nextTargets . shift ( ) ;
2020-02-15 12:54:55 +07:00
if ( target ) {
2020-04-08 18:46:43 +03:00
this . prevTargets . push ( { element : this.lastTarget , mid : this.currentMessageID } ) ;
this . openMedia ( appMessagesManager . getMessage ( target . mid ) , target . element ) ;
2020-02-15 12:54:55 +07:00
} else {
this . buttons . next . style . display = 'none' ;
}
2020-02-06 22:43:07 +07:00
} ) ;
2020-06-05 19:01:06 +03:00
[ this . buttons . download , this . buttons [ "menu-download" ] ] . forEach ( el = > {
2020-08-27 21:25:47 +03:00
el . addEventListener ( 'click' , this . onClickDownload ) ;
2020-02-14 16:30:06 +07:00
} ) ;
2020-02-15 12:54:55 +07:00
2020-06-21 15:25:17 +03:00
const forward = ( e : MouseEvent ) = > {
2020-09-24 02:37:22 +03:00
appSidebarRight . forwardTab . open ( [ this . currentMessageID ] ) ;
2020-06-05 19:01:06 +03:00
} ;
[ this . buttons . forward , this . buttons [ "menu-forward" ] ] . forEach ( el = > {
el . addEventListener ( 'click' , forward ) ;
2020-05-27 09:21:16 +03:00
} ) ;
2020-08-27 21:25:47 +03:00
this . wholeDiv . addEventListener ( 'click' , this . onClick ) ;
2020-05-30 09:44:54 +03:00
//this.content.mover.addEventListener('click', this.onClickBinded);
2020-02-15 18:14:58 +07:00
//this.content.mover.append(this.buttons.prev, this.buttons.next);
2020-05-30 09:44:54 +03:00
this . setNewMover ( ) ;
2020-08-28 22:53:10 +03:00
if ( touchSupport ) {
const swipeHandler = new SwipeHandler ( this . wholeDiv , ( xDiff , yDiff ) = > {
2020-09-01 15:53:46 +03:00
if ( VideoPlayer . isFullScreen ( ) ) {
return ;
}
2020-08-28 22:53:10 +03:00
//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 ;
} ) ;
}
2020-02-06 22:43:07 +07:00
}
2020-02-15 12:54:55 +07:00
2020-08-27 21:25:47 +03:00
onClickDownload = ( e : MouseEvent ) = > {
const message = appMessagesManager . getMessage ( this . currentMessageID ) ;
if ( message . media . photo ) {
appPhotosManager . savePhotoFile ( message . media . photo ) ;
} else {
2020-09-17 22:33:23 +03:00
let document : MyDocument = null ;
2020-08-27 21:25:47 +03:00
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 ) ;
}
}
} ;
onClick = ( e : MouseEvent ) = > {
if ( this . setMoverAnimationPromise ) return ;
const target = e . target as HTMLElement ;
if ( target . tagName == 'A' ) return ;
cancelEvent ( e ) ;
2020-08-28 22:53:10 +03:00
if ( touchSupport ) {
if ( this . highlightSwitchersTimeout ) {
clearTimeout ( this . highlightSwitchersTimeout ) ;
} else {
this . wholeDiv . classList . add ( 'highlight-switchers' ) ;
}
2020-09-20 01:38:00 +03:00
this . highlightSwitchersTimeout = window . setTimeout ( ( ) = > {
2020-08-28 22:53:10 +03:00
this . wholeDiv . classList . remove ( 'highlight-switchers' ) ;
this . highlightSwitchersTimeout = 0 ;
} , 3 e3 ) ;
return ;
}
2020-08-27 21:25:47 +03:00
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 ) = > {
2020-02-15 18:14:58 +07:00
//this.log('onKeyDown', e);
if ( e . key == 'ArrowRight' ) {
this . buttons . next . click ( ) ;
} else if ( e . key == 'ArrowLeft' ) {
this . buttons . prev . click ( ) ;
}
2020-08-27 21:25:47 +03:00
} ;
2020-02-15 18:14:58 +07:00
2020-08-27 21:25:47 +03:00
private async setMoverToTarget ( target : HTMLElement , closing = false , fromRight = 0 ) {
2020-08-26 23:31:07 +03:00
const mover = this . content . mover ;
2020-02-15 12:54:55 +07:00
2020-08-30 22:30:11 +03:00
if ( ! target ) {
target = this . content . container ;
}
2020-02-15 12:54:55 +07:00
if ( ! closing ) {
mover . innerHTML = '' ;
2020-05-30 09:44:54 +03:00
//mover.append(this.buttons.prev, this.buttons.next);
2020-02-15 12:54:55 +07:00
}
2020-08-27 21:25:47 +03:00
this . removeCenterFromMover ( mover ) ;
2020-02-15 12:54:55 +07:00
2020-08-26 23:31:07 +03:00
const wasActive = fromRight !== 0 ;
2020-02-15 18:14:58 +07:00
2020-08-26 23:31:07 +03:00
const delay = wasActive ? 350 : 200 ;
2020-04-08 18:46:43 +03:00
//let delay = wasActive ? 350 : 10000;
2020-02-15 18:14:58 +07:00
/ * i f ( w a s A c t i v e ) {
this . moveTheMover ( mover ) ;
mover = this . setNewMover ( ) ;
} * /
2020-05-18 04:21:58 +03:00
this . log ( 'setMoverToTarget' , target , closing , wasActive , fromRight ) ;
2020-02-15 18:14:58 +07:00
2020-08-19 17:11:25 +03:00
let realParent : HTMLElement ;
2020-04-08 18:46:43 +03:00
let rect : DOMRect ;
if ( target ) {
if ( target instanceof SVGImageElement || target . parentElement instanceof SVGForeignObjectElement ) {
realParent = findUpClassName ( target , 'attachment' ) ;
rect = realParent . getBoundingClientRect ( ) ;
} else {
2020-08-19 17:11:25 +03:00
realParent = target . parentElement as HTMLElement ;
2020-04-08 18:46:43 +03:00
rect = target . getBoundingClientRect ( ) ;
}
}
2020-08-26 23:31:07 +03:00
const containerRect = this . content . container . getBoundingClientRect ( ) ;
2020-02-15 18:14:58 +07:00
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 ;
}
2020-09-23 23:29:53 +03:00
transform += ` translate3d( ${ left } px, ${ top } px,0) ` ;
2020-08-27 21:25:47 +03:00
/ * i f ( w a s A c t i v e ) {
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) ` ;
} * /
2020-05-18 04:21:58 +03:00
let aspecter : HTMLDivElement ;
2020-09-01 15:53:46 +03:00
if ( target instanceof HTMLImageElement || target instanceof HTMLVideoElement || target . tagName == 'DIV' ) {
2020-05-18 04:21:58 +03:00
if ( mover . firstElementChild && mover . firstElementChild . classList . contains ( 'media-viewer-aspecter' ) ) {
aspecter = mover . firstElementChild as HTMLDivElement ;
2020-08-26 23:31:07 +03:00
const player = aspecter . querySelector ( '.ckin__player' ) ;
2020-05-18 04:21:58 +03:00
if ( player ) {
2020-08-26 23:31:07 +03:00
const video = player . firstElementChild as HTMLVideoElement ;
2020-05-18 04:21:58 +03:00
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' ) ;
2020-08-27 21:25:47 +03:00
aspecter . classList . add ( 'media-viewer-aspecter' /* , 'disable-hover' */ ) ;
2020-05-18 04:21:58 +03:00
mover . prepend ( aspecter ) ;
}
aspecter . style . cssText = ` width: ${ rect . width } px; height: ${ rect . height } px; transform: scale( ${ containerRect . width / rect . width } , ${ containerRect . height / rect . height } ); ` ;
}
2020-02-15 12:54:55 +07:00
mover . style . width = containerRect . width + 'px' ;
mover . style . height = containerRect . height + 'px' ;
2020-08-26 23:31:07 +03:00
const scaleX = rect . width / containerRect . width ;
const scaleY = rect . height / containerRect . height ;
2020-02-15 18:14:58 +07:00
if ( ! wasActive ) {
transform += ` scale( ${ scaleX } , ${ scaleY } ) ` ;
2020-04-08 18:46:43 +03:00
}
2020-02-15 18:14:58 +07:00
2020-04-08 18:46:43 +03:00
let borderRadius = window . getComputedStyle ( realParent ) . getPropertyValue ( 'border-radius' ) ;
2020-08-26 23:31:07 +03:00
const brSplitted = fillPropertyValue ( borderRadius ) as string [ ] ;
2020-04-08 18:46:43 +03:00
borderRadius = brSplitted . map ( r = > ( parseInt ( r ) / scaleX ) + 'px' ) . join ( ' ' ) ;
if ( ! wasActive ) {
2020-02-15 18:14:58 +07:00
mover . style . borderRadius = borderRadius ;
}
mover . style . transform = transform ;
2020-02-17 19:18:06 +07:00
/ * i f ( w a s A c t i v e ) {
2020-02-15 18:14:58 +07:00
this . log ( 'setMoverToTarget' , mover . style . transform ) ;
2020-02-17 19:18:06 +07:00
} * /
2020-02-15 15:22:33 +07:00
2020-04-08 18:46:43 +03:00
let path : SVGPathElement ;
2020-08-26 23:31:07 +03:00
const isOut = target . classList . contains ( 'is-out' ) ;
2020-04-08 18:46:43 +03:00
2020-08-27 21:25:47 +03:00
const deferred = this . setMoverAnimationPromise = deferredPromise < void > ( ) ;
const ret = { onAnimationEnd : deferred } ;
this . setMoverAnimationPromise . then ( ( ) = > {
this . setMoverAnimationPromise = null ;
} ) ;
2020-02-15 12:54:55 +07:00
if ( ! closing ) {
2020-05-27 09:21:16 +03:00
let mediaElement : HTMLImageElement | HTMLVideoElement ;
let src : string ;
if ( target . tagName == 'DIV' ) { // useContainerAsTarget
if ( target . firstElementChild ) {
mediaElement = new Image ( ) ;
src = ( target . firstElementChild as HTMLImageElement ) . src ;
mover . append ( mediaElement ) ;
}
/ * m e d i a E l e m e n t = n e w I m a g e ( ) ;
src = target . style . backgroundImage . slice ( 5 , - 2 ) ; * /
2020-05-18 04:21:58 +03:00
} else if ( target instanceof HTMLImageElement ) {
2020-05-27 09:21:16 +03:00
mediaElement = new Image ( ) ;
src = target . src ;
2020-05-18 04:21:58 +03:00
} else if ( target instanceof HTMLVideoElement ) {
2020-08-26 23:31:07 +03:00
const video = mediaElement = document . createElement ( 'video' ) ;
2020-08-24 17:09:31 +03:00
video . src = target ? . src ;
2020-04-08 18:46:43 +03:00
} else if ( target instanceof SVGSVGElement ) {
2020-08-26 23:31:07 +03:00
const clipID = target . dataset . clipID ;
const newClipID = clipID + '-mv' ;
2020-04-08 18:46:43 +03:00
2020-08-26 23:31:07 +03:00
const { width , height } = containerRect ;
2020-04-08 18:46:43 +03:00
2020-08-26 23:31:07 +03:00
const newSvg = document . createElementNS ( "http://www.w3.org/2000/svg" , "svg" ) ;
2020-04-08 18:46:43 +03:00
newSvg . setAttributeNS ( null , 'width' , '' + width ) ;
newSvg . setAttributeNS ( null , 'height' , '' + height ) ;
2020-08-27 21:25:47 +03:00
// нижние два свойства для масштабирования
newSvg . setAttributeNS ( null , 'viewBox' , ` 0 0 ${ width } ${ height } ` ) ;
newSvg . setAttributeNS ( null , 'preserveAspectRatio' , 'xMidYMid meet' ) ;
2020-04-08 18:46:43 +03:00
newSvg . insertAdjacentHTML ( 'beforeend' , target . firstElementChild . outerHTML . replace ( clipID , newClipID ) ) ;
newSvg . insertAdjacentHTML ( 'beforeend' , target . lastElementChild . outerHTML . replace ( clipID , newClipID ) ) ;
// теперь надо выставить новую позицию для хвостика
2020-08-26 23:31:07 +03:00
const defs = newSvg . firstElementChild ;
const use = defs . firstElementChild . firstElementChild as SVGUseElement ;
2020-04-08 18:46:43 +03:00
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 ;
2020-08-26 23:31:07 +03:00
const br : [ number , number , number , number ] = borderRadius . split ( ' ' ) . map ( v = > parseInt ( v ) ) as any ;
2020-04-08 18:46:43 +03:00
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 ) ;
}
2020-08-26 23:31:07 +03:00
const foreignObject = newSvg . lastElementChild ;
2020-05-27 09:21:16 +03:00
foreignObject . setAttributeNS ( null , 'width' , '' + containerRect . width ) ;
foreignObject . setAttributeNS ( null , 'height' , '' + containerRect . height ) ;
2020-04-08 18:46:43 +03:00
mover . prepend ( newSvg ) ;
2020-02-15 12:54:55 +07:00
}
2020-05-18 04:21:58 +03:00
if ( aspecter ) {
aspecter . style . borderRadius = borderRadius ;
2020-09-01 15:53:46 +03:00
if ( mediaElement ) {
aspecter . append ( mediaElement ) ;
}
2020-05-27 09:21:16 +03:00
}
mediaElement = mover . querySelector ( 'video, img' ) ;
2020-09-01 15:53:46 +03:00
if ( mediaElement instanceof HTMLImageElement ) {
mediaElement . classList . add ( 'thumbnail' ) ;
if ( ! aspecter ) {
mediaElement . style . width = containerRect . width + 'px' ;
mediaElement . style . height = containerRect . height + 'px' ;
}
2020-05-27 09:21:16 +03:00
2020-09-01 15:53:46 +03:00
if ( src ) {
await new Promise ( ( resolve , reject ) = > {
mediaElement . addEventListener ( 'load' , resolve ) ;
if ( src ) {
mediaElement . src = src ;
}
} ) ;
}
2020-06-16 23:48:08 +03:00
} / * else if ( mediaElement instanceof HTMLVideoElement && mediaElement . firstElementChild && ( ( mediaElement . firstElementChild as HTMLSourceElement ) . src || src ) ) {
2020-05-27 09:21:16 +03:00
await new Promise ( ( resolve , reject ) = > {
mediaElement . addEventListener ( 'loadeddata' , resolve ) ;
if ( src ) {
( mediaElement . firstElementChild as HTMLSourceElement ) . src = src ;
}
} ) ;
2020-06-16 23:48:08 +03:00
} * /
2020-02-15 12:54:55 +07:00
mover . style . display = '' ;
2020-02-15 18:14:58 +07:00
2020-05-27 09:21:16 +03:00
window . requestAnimationFrame ( ( ) = > {
2020-02-15 18:14:58 +07:00
mover . classList . add ( wasActive ? 'moving' : 'active' ) ;
2020-05-27 09:21:16 +03:00
} ) ;
2020-02-15 12:54:55 +07:00
} else {
2020-08-27 21:25:47 +03:00
/ * i f ( m o v e r . c l a s s L i s t . c o n t a i n s ( ' c e n t e r ' ) ) {
mover . classList . remove ( 'center' ) ;
void mover . offsetLeft ; // reflow
} * /
2020-04-08 18:46:43 +03:00
if ( target instanceof SVGSVGElement ) {
path = mover . querySelector ( 'path' ) ;
if ( path ) {
this . sizeTailPath ( path , containerRect , scaleX , delay , false , isOut , borderRadius ) ;
}
}
2020-05-27 09:21:16 +03:00
if ( target . classList . contains ( 'media-viewer-media' ) ) {
mover . classList . add ( 'hiding' ) ;
}
2020-02-15 12:54:55 +07:00
setTimeout ( ( ) = > {
2020-05-30 09:44:54 +03:00
this . wholeDiv . classList . remove ( 'active' ) ;
2020-02-15 18:14:58 +07:00
} , 0 ) ;
setTimeout ( ( ) = > {
mover . style . borderRadius = borderRadius ;
if ( mover . firstElementChild ) {
( mover . firstElementChild as HTMLElement ) . style . borderRadius = borderRadius ;
}
2020-02-15 22:20:38 +07:00
} , delay / 2 ) ;
2020-02-15 18:14:58 +07:00
2020-02-15 12:54:55 +07:00
setTimeout ( ( ) = > {
mover . innerHTML = '' ;
2020-05-27 09:21:16 +03:00
mover . classList . remove ( 'moving' , 'active' , 'hiding' ) ;
2020-09-01 15:53:46 +03:00
mover . style . cssText = 'display: none;' ;
2020-08-27 21:25:47 +03:00
deferred . resolve ( ) ;
2020-02-15 18:14:58 +07:00
} , delay ) ;
2020-02-15 12:54:55 +07:00
2020-08-27 21:25:47 +03:00
return ret ;
2020-05-27 09:21:16 +03:00
}
2020-02-15 15:22:33 +07:00
2020-05-27 09:21:16 +03:00
//await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise ( ( resolve ) = > window . requestAnimationFrame ( resolve ) ) ;
2020-05-18 04:21:58 +03:00
2020-08-27 21:25:47 +03:00
// чтобы проверить установленную позицию - раскомментировать
2020-05-30 09:44:54 +03:00
//throw '';
2020-09-23 23:29:53 +03:00
mover . style . transform = ` translate3d( ${ containerRect . left } px, ${ containerRect . top } px,0) scale(1,1) ` ;
2020-08-27 21:25:47 +03:00
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
2020-02-15 18:14:58 +07:00
2020-05-27 09:21:16 +03:00
if ( aspecter ) {
this . setFullAspect ( aspecter , containerRect , rect ) ;
}
2020-04-08 18:46:43 +03:00
2020-05-27 09:21:16 +03:00
setTimeout ( ( ) = > {
mover . style . borderRadius = '' ;
2020-05-18 04:21:58 +03:00
2020-05-27 09:21:16 +03:00
if ( mover . firstElementChild ) {
( mover . firstElementChild as HTMLElement ) . style . borderRadius = '' ;
}
} , delay / 2 ) ;
2020-05-20 17:25:23 +03:00
2020-05-27 09:21:16 +03:00
mover . dataset . timeout = '' + setTimeout ( ( ) = > {
mover . classList . remove ( 'moving' ) ;
2020-05-18 04:21:58 +03:00
2020-05-27 09:21:16 +03:00
if ( aspecter ) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
2020-08-27 21:25:47 +03:00
if ( mover . querySelector ( 'video' ) || true ) {
2020-05-30 09:44:54 +03:00
mover . classList . remove ( 'active' ) ;
aspecter . style . cssText = '' ;
void mover . offsetLeft ; // reflow
}
2020-08-27 21:25:47 +03:00
//aspecter.classList.remove('disable-hover');
2020-04-08 18:46:43 +03:00
}
2020-05-27 09:21:16 +03:00
2020-08-27 21:25:47 +03:00
// эти строки нужны для установки центральной позиции, в случае ресайза это будет нужно
mover . classList . add ( 'center' , 'no-transition' ) ;
/ * m o v e r . s t y l e . l e f t = m o v e r . s t y l e . t o p = ' 5 0 % ' ;
mover . style . transform = 'translate(-50%, -50%)' ;
void mover . offsetLeft ; // reflow */
// это уже нужно для будущих анимаций
2020-05-27 09:21:16 +03:00
mover . classList . add ( 'active' ) ;
delete mover . dataset . timeout ;
2020-08-27 21:25:47 +03:00
deferred . resolve ( ) ;
2020-05-27 09:21:16 +03:00
} , delay ) ;
if ( path ) {
this . sizeTailPath ( path , containerRect , scaleX , delay , true , isOut , borderRadius ) ;
}
2020-08-27 21:25:47 +03:00
return ret ;
2020-02-15 12:54:55 +07:00
}
2020-02-15 18:14:58 +07:00
2020-05-18 04:21:58 +03:00
private setFullAspect ( aspecter : HTMLDivElement , containerRect : DOMRect , rect : DOMRect ) {
2020-06-20 04:11:24 +03:00
/ * l e t m e d i a = a s p e c t e r . f i r s t E l e m e n t C h i l d ;
2020-05-18 04:21:58 +03:00
let proportion : number ;
if ( media instanceof HTMLImageElement ) {
proportion = media . naturalWidth / media . naturalHeight ;
} else if ( media instanceof HTMLVideoElement ) {
proportion = media . videoWidth / media . videoHeight ;
2020-06-20 04:11:24 +03:00
} * /
const proportion = containerRect . width / containerRect . height ;
2020-05-18 04:21:58 +03:00
let { width , height } = rect ;
2020-05-30 09:44:54 +03:00
/ * i f ( p r o p o r t i o n = = 1 ) {
2020-05-18 04:21:58 +03:00
aspecter . style . cssText = '' ;
2020-05-30 09:44:54 +03:00
} else { * /
2020-05-18 04:21:58 +03:00
if ( proportion > 0 ) {
width = height * proportion ;
} else {
height = width * proportion ;
}
2020-05-27 09:21:16 +03:00
//this.log('will set style aspecter:', `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`);
2020-05-18 04:21:58 +03:00
aspecter . style . cssText = ` width: ${ width } px; height: ${ height } px; transform: scale( ${ containerRect . width / width } , ${ containerRect . height / height } ); ` ;
2020-05-30 09:44:54 +03:00
//}
2020-05-18 04:21:58 +03:00
}
2020-08-27 21:25:47 +03:00
private sizeTailPath ( path : SVGPathElement , rect : DOMRect , scaleX : number , delay : number , upscale : boolean , isOut : boolean , borderRadius : string ) {
2020-08-26 23:31:07 +03:00
const start = Date . now ( ) ;
const { width , height } = rect ;
2020-04-08 18:46:43 +03:00
delay = delay / 2 ;
2020-08-26 23:31:07 +03:00
const br = borderRadius . split ( ' ' ) . map ( v = > parseInt ( v ) ) ;
2020-04-08 18:46:43 +03:00
2020-08-26 23:31:07 +03:00
const step = ( ) = > {
const diff = Date . now ( ) - start ;
2020-04-08 18:46:43 +03:00
let progress = diff / delay ;
if ( progress > 1 ) progress = 1 ;
if ( upscale ) progress = 1 - progress ;
2020-08-26 23:31:07 +03:00
const _br : [ number , number , number , number ] = br . map ( v = > v * progress ) as any ;
2020-04-08 18:46:43 +03:00
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 ( ) ;
}
2020-08-27 21:25:47 +03:00
private removeCenterFromMover ( mover : HTMLDivElement ) {
if ( mover . classList . contains ( 'center' ) ) {
2020-09-01 15:53:46 +03:00
//const rect = mover.getBoundingClientRect();
const rect = this . content . container . getBoundingClientRect ( ) ;
2020-09-23 23:29:53 +03:00
mover . style . transform = ` translate3d( ${ rect . left } px, ${ rect . top } px,0) ` ;
2020-08-27 21:25:47 +03:00
mover . classList . remove ( 'center' ) ;
void mover . offsetLeft ; // reflow
mover . classList . remove ( 'no-transition' ) ;
}
}
private moveTheMover ( mover : HTMLDivElement , toLeft = true ) {
2020-08-26 23:31:07 +03:00
const windowW = appPhotosManager . windowW ;
2020-02-15 18:14:58 +07:00
2020-08-27 21:25:47 +03:00
this . removeCenterFromMover ( mover ) ;
2020-05-18 04:21:58 +03:00
//mover.classList.remove('active');
2020-02-15 18:14:58 +07:00
mover . classList . add ( 'moving' ) ;
2020-05-18 04:21:58 +03:00
if ( mover . dataset . timeout ) { // и это тоже всё из-за скейла видео, так бы это не нужно было
clearTimeout ( + mover . dataset . timeout ) ;
}
2020-08-26 23:31:07 +03:00
const rect = mover . getBoundingClientRect ( ) ;
2020-02-15 18:14:58 +07:00
2020-09-23 23:29:53 +03:00
const newTransform = mover . style . transform . replace ( /translate3d\((.+?),/ , ( match , p1 ) = > {
2020-08-27 21:25:47 +03:00
const x = toLeft ? - rect.width : windowW ;
//const x = toLeft ? -(rect.right + (rect.width / 2)) : windowW / 2;
2020-02-15 18:14:58 +07:00
return match . replace ( p1 , x + 'px' ) ;
} ) ;
2020-02-17 19:18:06 +07:00
////////this.log('set newTransform:', newTransform, mover.style.transform, toLeft);
2020-02-15 18:14:58 +07:00
mover . style . transform = newTransform ;
setTimeout ( ( ) = > {
mover . remove ( ) ;
} , 350 ) ;
}
2020-08-27 21:25:47 +03:00
private setNewMover() {
2020-08-26 23:31:07 +03:00
const newMover = document . createElement ( 'div' ) ;
2020-02-15 18:14:58 +07:00
newMover . classList . add ( 'media-viewer-mover' ) ;
2020-05-30 09:44:54 +03:00
if ( this . content . mover ) {
2020-08-26 23:31:07 +03:00
const oldMover = this . content . mover ;
2020-05-30 09:44:54 +03:00
oldMover . parentElement . append ( newMover ) ;
} else {
this . wholeDiv . append ( newMover ) ;
}
2020-02-15 18:14:58 +07:00
return this . content . mover = newMover ;
}
2020-04-08 18:46:43 +03:00
2020-08-26 23:31:07 +03:00
/ * p u b l i c i s E l e m e n t V i s i b l e ( c o n t a i n e r : H T M L E l e m e n t , t a r g e t : H T M L E l e m e n t ) {
const rect = container . getBoundingClientRect ( ) ;
const targetRect = target . getBoundingClientRect ( ) ;
2020-04-08 18:46:43 +03:00
return targetRect . bottom > rect . top && targetRect . top < rect . bottom ;
2020-08-26 23:31:07 +03:00
} * /
2020-04-08 18:46:43 +03:00
// нет смысла делать проверку для reverse и loadMediaPromise
2020-08-27 21:25:47 +03:00
private loadMoreMedia ( older = true ) {
2020-04-08 18:46:43 +03:00
//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 ;
2020-08-26 23:31:07 +03:00
const loadCount = 50 ;
const backLimit = older ? 0 : loadCount ;
2020-04-08 18:46:43 +03:00
let maxID = this . currentMessageID ;
2020-02-06 22:43:07 +07:00
2020-04-08 18:46:43 +03:00
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 ;
2020-08-26 23:31:07 +03:00
const peerID = this . peerID ;
2020-04-08 18:46:43 +03:00
2020-08-26 23:31:07 +03:00
const promise = appMessagesManager . getSearch ( peerID , '' ,
2020-04-08 18:46:43 +03:00
{ _ : '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 ) {
/ * i f ( t h i s . r e v e r s e ) {
if ( older ) this . loadedAllMediaUp = true ;
else this . loadedAllMediaDown = true ;
} else { * /
if ( older ) this . loadedAllMediaDown = true ;
else this . loadedAllMediaUp = true ;
//}
}
2020-08-26 23:31:07 +03:00
const method = older ? value.history.forEach : value.history.forEachReverse ;
2020-04-08 18:46:43 +03:00
method . call ( value . history , mid = > {
2020-08-26 23:31:07 +03:00
const message = appMessagesManager . getMessage ( mid ) ;
const media = message . media ;
2020-04-08 18:46:43 +03:00
if ( ! media || ! ( media . photo || media . document || ( media . webpage && media . webpage . document ) ) ) return ;
if ( media . _ == 'document' && media . type != 'video' ) return ;
2020-08-26 23:31:07 +03:00
const t = { element : null as HTMLElement , mid : mid } ;
2020-04-08 18:46:43 +03:00
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 ;
}
2020-08-27 21:25:47 +03:00
private updateMediaSource ( target : HTMLElement , url : string , tagName : 'video' | 'img' ) {
2020-04-08 18:46:43 +03:00
//if(target instanceof SVGSVGElement) {
2020-08-26 23:31:07 +03:00
const el = target . querySelector ( tagName ) as HTMLElement ;
2020-04-16 03:48:41 +03:00
renderImageFromUrl ( el , url ) ;
2020-04-08 18:46:43 +03:00
/ * } e l s e {
} * /
}
2020-06-05 19:01:06 +03:00
public async openMedia ( message : any , target? : HTMLElement , reverse = false , targetContainer? : HTMLElement ,
2020-05-02 00:28:40 +03:00
prevTargets : AppMediaViewer [ 'prevTargets' ] = [ ] , nextTargets : AppMediaViewer [ 'prevTargets' ] = [ ] , needLoadMore = true ) {
2020-05-27 09:21:16 +03:00
if ( this . setMoverPromise ) return this . setMoverPromise ;
this . log ( 'openMedia doc:' , message ) ;
2020-05-26 18:04:06 +03:00
const media = message . media . photo || message . media . document || message . media . webpage . document || message . media . webpage . photo ;
2020-02-06 22:43:07 +07:00
2020-09-17 22:33:23 +03:00
const isVideo = ( media as MyDocument ) . type == 'video' || ( media as MyDocument ) . type == 'gif' ;
2020-05-26 18:04:06 +03:00
const isFirstOpen = ! this . peerID ;
2020-04-08 18:46:43 +03:00
if ( isFirstOpen ) {
this . peerID = $rootScope . selectedPeerID ;
2020-06-05 19:01:06 +03:00
//this.targetContainer = targetContainer;
2020-04-08 18:46:43 +03:00
this . prevTargets = prevTargets ;
this . nextTargets = nextTargets ;
this . reverse = reverse ;
2020-05-02 00:28:40 +03:00
this . needLoadMore = needLoadMore ;
2020-04-08 18:46:43 +03:00
//this.loadMore = loadMore;
2020-06-05 19:01:06 +03:00
2020-09-24 02:37:22 +03:00
if ( appSidebarRight . historyTabIDs . slice ( - 1 ) [ 0 ] == AppSidebarRight . SLIDERITEMSIDS . forward ) {
appSidebarRight . forwardTab . closeBtn . click ( ) ;
2020-06-05 19:01:06 +03:00
await new Promise ( ( resolve ) = > setTimeout ( resolve , 200 ) ) ;
}
2020-04-08 18:46:43 +03:00
}
/ * i f ( t h i s . n e x t T a r g e t s . l e n g t h < 1 0 & & t h i s . l o a d M o r e ) {
this . loadMore ( ) ;
} * /
2020-02-15 18:14:58 +07:00
let fromRight = 0 ;
2020-04-08 18:46:43 +03:00
if ( ! isFirstOpen ) {
//if(this.lastTarget === prevTarget) {
if ( this . reverse ) fromRight = this . currentMessageID < message . mid ? 1 : - 1 ;
else fromRight = this . currentMessageID > message . mid ? 1 : - 1 ;
2020-02-15 18:14:58 +07:00
}
2020-04-08 18:46:43 +03:00
//if(prevTarget && (!prevTarget.parentElement || !this.isElementVisible(this.targetContainer, prevTarget))) prevTarget = null;
//if(nextTarget && (!nextTarget.parentElement || !this.isElementVisible(this.targetContainer, nextTarget))) nextTarget = null;
this . buttons . prev . style . display = this . prevTargets . length ? '' : 'none' ;
this . buttons . next . style . display = this . nextTargets . length ? '' : 'none' ;
2020-02-06 22:43:07 +07:00
2020-05-26 18:04:06 +03:00
const container = this . content . container ;
const useContainerAsTarget = ! target ;
2020-04-08 18:46:43 +03:00
if ( useContainerAsTarget ) target = container ;
2020-02-06 22:43:07 +07:00
this . currentMessageID = message . mid ;
2020-02-15 18:14:58 +07:00
this . lastTarget = target ;
2020-04-08 18:46:43 +03:00
2020-05-02 00:28:40 +03:00
if ( this . needLoadMore ) {
if ( this . nextTargets . length < 20 ) {
this . loadMoreMedia ( ! this . reverse ) ;
}
if ( this . prevTargets . length < 20 ) {
this . loadMoreMedia ( this . reverse ) ;
}
2020-04-08 18:46:43 +03:00
}
2020-02-06 22:43:07 +07:00
if ( container . firstElementChild ) {
2020-02-07 13:38:55 +07:00
container . innerHTML = '' ;
2020-02-06 22:43:07 +07:00
}
2020-05-26 18:04:06 +03:00
const date = new Date ( media . date * 1000 ) ;
const months = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ] ;
2020-02-06 22:43:07 +07:00
2020-05-26 18:04:06 +03:00
const dateStr = months [ date . getMonth ( ) ] + ' ' + date . getDate ( ) + ' at ' + date . getHours ( ) + ':' + ( '0' + date . getMinutes ( ) ) . slice ( - 2 ) ;
2020-02-06 22:43:07 +07:00
this . author . date . innerText = dateStr ;
2020-05-26 18:04:06 +03:00
const name = appPeersManager . getPeerTitle ( message . fromID ) ;
2020-02-10 21:51:24 +07:00
this . author . nameEl . innerHTML = name ;
2020-02-06 22:43:07 +07:00
if ( message . message ) {
this . content . caption . innerHTML = RichTextProcessor . wrapRichText ( message . message , {
entities : message.totalEntities
} ) ;
} else {
this . content . caption . innerHTML = '' ;
}
2020-05-27 09:21:16 +03:00
let oldAvatar = this . author . avatarEl ;
2020-08-27 21:25:47 +03:00
this . author . avatarEl = ( this . author . avatarEl . cloneNode ( ) as AvatarElement ) ;
2020-05-13 18:26:40 +03:00
this . author . avatarEl . setAttribute ( 'peer' , '' + message . fromID ) ;
2020-05-27 09:21:16 +03:00
oldAvatar . parentElement . replaceChild ( this . author . avatarEl , oldAvatar ) ;
2020-02-07 21:17:39 +07:00
2020-02-15 12:54:55 +07:00
// ok set
2020-02-15 18:14:58 +07:00
2020-05-26 18:04:06 +03:00
const wasActive = fromRight !== 0 ;
2020-02-15 18:14:58 +07:00
if ( wasActive ) {
this . moveTheMover ( this . content . mover , fromRight === 1 ) ;
this . setNewMover ( ) ;
} else {
2020-08-27 21:25:47 +03:00
window . addEventListener ( 'keydown' , this . onKeyDown ) ;
2020-05-30 09:44:54 +03:00
this . wholeDiv . classList . add ( 'active' ) ;
2020-02-15 18:14:58 +07:00
}
2020-02-17 19:18:06 +07:00
////////this.log('wasActive:', wasActive);
2020-02-15 18:14:58 +07:00
2020-05-26 18:04:06 +03:00
const mover = this . content . mover ;
//const maxWidth = appPhotosManager.windowW - 16;
2020-06-21 15:25:17 +03:00
const maxWidth = mediaSizes . isMobile ? this . pageEl.scrollWidth : this.pageEl.scrollWidth - 16 ;
2020-05-26 18:04:06 +03:00
const maxHeight = appPhotosManager . windowH - 100 ;
2020-08-25 19:26:49 +03:00
const size = appPhotosManager . setAttachmentSize ( media , container , maxWidth , maxHeight ) ;
2020-05-26 18:04:06 +03:00
// need after setAttachmentSize
2020-05-27 09:21:16 +03:00
/ * i f ( u s e C o n t a i n e r A s T a r g e t ) {
2020-05-23 08:31:18 +03:00
target = target . querySelector ( 'img, video' ) || target ;
2020-05-27 09:21:16 +03:00
} * /
2020-05-23 08:31:18 +03:00
2020-08-27 21:25:47 +03:00
const preloader = media . supportsStreaming ? this . preloaderStreamable : this.preloader ;
2020-05-27 09:21:16 +03:00
let setMoverPromise : Promise < void > ;
2020-02-06 22:43:07 +07:00
if ( isVideo ) {
2020-02-17 19:18:06 +07:00
////////this.log('will wrap video', media, size);
2020-02-15 12:54:55 +07:00
2020-08-30 13:43:57 +03:00
// потому что для safari нужно создать элемент из event'а
const video = document . createElement ( 'video' ) ;
2020-08-27 21:25:47 +03:00
setMoverPromise = this . setMoverToTarget ( target , false , fromRight ) . then ( ( { onAnimationEnd } ) = > {
2020-05-18 04:21:58 +03:00
//return; // set and don't move
2020-02-15 18:14:58 +07:00
//if(wasActive) return;
2020-04-08 18:46:43 +03:00
//return;
2020-02-15 12:54:55 +07:00
2020-06-16 23:48:08 +03:00
const div = mover . firstElementChild && mover . firstElementChild . classList . contains ( 'media-viewer-aspecter' ) ? mover.firstElementChild : mover ;
2020-08-30 13:43:57 +03:00
//const video = mover.querySelector('video') || document.createElement('video');
const moverVideo = mover . querySelector ( 'video' ) ;
if ( moverVideo ) {
moverVideo . remove ( ) ;
}
2020-08-27 21:25:47 +03:00
//video.src = '';
2020-04-08 18:46:43 +03:00
2020-06-06 23:38:48 +03:00
video . setAttribute ( 'playsinline' , '' ) ;
2020-09-01 15:53:46 +03:00
if ( isSafari ) {
video . autoplay = true ;
}
2020-05-13 18:26:40 +03:00
if ( media . type == 'gif' ) {
2020-06-06 23:38:48 +03:00
video . muted = true ;
2020-05-13 18:26:40 +03:00
video . autoplay = true ;
2020-05-27 09:21:16 +03:00
video . loop = true ;
2020-05-13 18:26:40 +03:00
}
2020-08-27 21:25:47 +03:00
if ( ! video . parentElement ) {
div . append ( video ) ;
}
const canPlayThrough = new Promise ( ( resolve ) = > {
2020-09-01 15:53:46 +03:00
video . addEventListener ( 'canplay' , resolve , { once : true } ) ;
2020-08-27 21:25:47 +03:00
} ) ;
2020-08-22 19:53:59 +03:00
const createPlayer = ( ) = > {
2020-05-18 04:21:58 +03:00
if ( media . type != 'gif' ) {
video . dataset . ckin = 'default' ;
video . dataset . overlay = '1' ;
2020-08-26 14:59:32 +03:00
// fix for simultaneous play
2020-08-29 14:45:37 +03:00
appMediaPlaybackController . pause ( ) ;
appMediaPlaybackController . willBePlayedMedia = null ;
2020-08-27 21:25:47 +03:00
Promise . all ( [ canPlayThrough , onAnimationEnd ] ) . then ( ( ) = > {
const player = new VideoPlayer ( video , true , media . supportsStreaming ) ;
/ * d i v . a p p e n d ( v i d e o ) ;
mover . append ( player . wrapper ) ; * /
} ) ;
2020-08-26 23:31:07 +03:00
}
2020-05-18 04:21:58 +03:00
} ;
2020-08-27 21:25:47 +03:00
if ( media . supportsStreaming ) {
onAnimationEnd . then ( ( ) = > {
if ( video . readyState < video . HAVE_FUTURE_DATA ) {
preloader . attach ( mover , true ) ;
}
/ * c a n P l a y T h r o u g h . t h e n ( ( ) = > {
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 ( ) ;
}
2020-04-08 18:46:43 +03:00
2020-08-27 21:25:47 +03:00
//if(!video.src || media.url != video.src) {
2020-06-16 23:48:08 +03:00
const load = ( ) = > {
2020-08-27 21:25:47 +03:00
const promise = media . supportsStreaming ? Promise . resolve ( ) : appDocsManager . downloadDocNew ( media ) ;
2020-08-22 19:53:59 +03:00
2020-08-27 21:25:47 +03:00
if ( ! media . supportsStreaming ) {
onAnimationEnd . then ( ( ) = > {
preloader . attach ( mover , true , promise ) ;
} ) ;
}
2020-06-16 23:48:08 +03:00
2020-08-27 21:25:47 +03:00
( promise as Promise < any > ) . then ( async ( ) = > {
2020-05-27 09:21:16 +03:00
if ( this . currentMessageID != message . mid ) {
this . log . warn ( 'media viewer changed video' ) ;
return ;
2020-04-08 18:46:43 +03:00
}
2020-06-16 23:48:08 +03:00
2020-08-22 19:53:59 +03:00
const url = media . url ;
2020-08-27 21:25:47 +03:00
if ( target instanceof SVGSVGElement /* && (video.parentElement || !isSafari) */ ) { // if video exists
//if(!video.parentElement) {
2020-06-16 23:48:08 +03:00
div . firstElementChild . lastElementChild . append ( video ) ;
2020-08-27 21:25:47 +03:00
//}
2020-06-16 23:48:08 +03:00
2020-08-24 17:09:31 +03:00
this . updateMediaSource ( mover , url , 'video' ) ;
2020-05-27 09:21:16 +03:00
} else {
2020-08-24 17:09:31 +03:00
renderImageFromUrl ( video , url ) ;
2020-05-18 04:21:58 +03:00
}
2020-06-16 23:48:08 +03:00
2020-08-26 23:31:07 +03:00
createPlayer ( ) ;
2020-05-27 09:21:16 +03:00
} ) ;
2020-04-08 18:46:43 +03:00
2020-05-27 09:21:16 +03:00
return promise ;
} ;
2020-04-08 18:46:43 +03:00
2020-09-21 20:34:19 +03:00
this . lazyLoadQueue . unshift ( { load } ) ;
2020-08-27 21:25:47 +03:00
//} else createPlayer();
2020-05-27 09:21:16 +03:00
} ) ;
2020-02-06 22:43:07 +07:00
} else {
2020-08-27 21:25:47 +03:00
setMoverPromise = this . setMoverToTarget ( target , false , fromRight ) . then ( ( { onAnimationEnd } ) = > {
2020-05-18 04:21:58 +03:00
//return; // set and don't move
2020-02-15 18:14:58 +07:00
//if(wasActive) return;
2020-04-08 18:46:43 +03:00
//return;
2020-05-27 09:21:16 +03:00
2020-09-01 15:53:46 +03:00
const load = ( ) = > {
const cancellablePromise = appPhotosManager . preloadPhoto ( media . id , size ) ;
2020-08-27 21:25:47 +03:00
onAnimationEnd . then ( ( ) = > {
this . preloader . attach ( mover , true , cancellablePromise ) ;
} ) ;
2020-05-27 09:21:16 +03:00
cancellablePromise . then ( ( ) = > {
if ( this . currentMessageID != message . mid ) {
this . log . warn ( 'media viewer changed photo' ) ;
return ;
2020-05-18 04:21:58 +03:00
}
2020-05-27 09:21:16 +03:00
///////this.log('indochina', blob);
2020-09-01 15:53:46 +03:00
const url = media . url ;
2020-05-27 09:21:16 +03:00
if ( target instanceof SVGSVGElement ) {
this . updateMediaSource ( target , url , 'img' ) ;
this . updateMediaSource ( mover , url , 'img' ) ;
2020-09-01 15:53:46 +03:00
2020-09-02 00:16:04 +03:00
if ( mediaSizes . isMobile ) {
const imgs = mover . querySelectorAll ( 'img' ) ;
if ( imgs && imgs . length ) {
imgs . forEach ( img = > {
img . classList . remove ( 'thumbnail' ) ; // может здесь это вообще не нужно
} ) ;
}
}
2020-05-27 09:21:16 +03:00
} else {
2020-09-01 15:53:46 +03:00
const div = mover . firstElementChild && mover . firstElementChild . classList . contains ( 'media-viewer-aspecter' ) ? mover.firstElementChild : mover ;
2020-05-27 09:21:16 +03:00
let image = div . firstElementChild as HTMLImageElement ;
if ( ! image || image . tagName != 'IMG' ) {
image = new Image ( ) ;
}
//this.log('will renderImageFromUrl:', image, div, target);
2020-08-25 12:39:39 +03:00
renderImageFromUrl ( image , url , ( ) = > {
2020-09-02 00:16:04 +03:00
if ( mediaSizes . isMobile ) {
image . classList . remove ( 'thumbnail' ) ; // может здесь это вообще не нужно
}
2020-05-27 09:21:16 +03:00
div . append ( image ) ;
} ) ;
}
this . preloader . detach ( ) ;
} ) . catch ( err = > {
this . log . error ( err ) ;
} ) ;
2020-05-18 04:21:58 +03:00
2020-05-27 09:21:16 +03:00
return cancellablePromise ;
} ;
2020-02-15 12:54:55 +07:00
2020-09-21 20:34:19 +03:00
this . lazyLoadQueue . unshift ( { load } ) ;
2020-05-27 09:21:16 +03:00
} ) ;
2020-02-06 22:43:07 +07:00
}
2020-05-27 09:21:16 +03:00
2020-08-27 21:25:47 +03:00
return this . setMoverPromise = setMoverPromise . catch ( ( ) = > {
this . setMoverAnimationPromise = null ;
} ) . finally ( ( ) = > {
2020-05-27 09:21:16 +03:00
this . setMoverPromise = null ;
} ) ;
2020-02-06 22:43:07 +07:00
}
}
export default new AppMediaViewer ( ) ;