goog.module('gep.components.MediaLayer');
const AbstractLayer = goog.require('gep.components.AbstractLayer');
const {LayerType} = goog.require('gep.provider.LayerProvider');
const {ExhibitionProvider,ExhibitionElementModel} = goog.require('gep.provider.ExhibitionProvider');
const VideoProvider = goog.require('gep.provider.VideoProvider');
const libs = goog.require('gep.net.libs');
const {createDom,getAncestorByClass} = goog.require('goog.dom');
const TagName = goog.require('goog.dom.TagName');
const dataset = goog.require('goog.dom.dataset');
const classlist = goog.require('goog.dom.classlist');
const {listen,listenOnce,unlisten,EventType,Event} = goog.require('goog.events');
const {setStyle} = goog.require('goog.style');
const {Size} = goog.require('goog.math');
/**
* Component that controls the display of the media layer and its contents.
* @extends {AbstractLayer}
*/
class MediaLayer extends AbstractLayer
{
constructor()
{
super(LayerType.MEDIA);
/**
* Reference to ExhibitionProvider to communicate with the backend and provide and monitor all information that dynamically changes the application.
* @type {ExhibitionProvider}
* @private
*/
this.exhibitionProvider_ = ExhibitionProvider.getInstance();
/**
* Reference to VideoProvider for creating and monitoring Vimeo video instances.
* @type {VideoProvider}
* @private
*/
this.videoProvider_ = VideoProvider.getInstance();
/**
* Reference to the data model of the artwork selected by the user. The data model contains all the information from the backend.
* @type {ExhibitionElementModel}
* @private
*/
this.artwork_ = null;
/**
* Reference to the created Swiper object. More information in the Swiper api documention {@link https://swiperjs.com/swiper-api}
* @type {Swiper}
* @private
*/
this.swiper_ = null;
/**
* Reference to an array of buttons in the content of this layer that let create and start the playback of a vimeo video instance.
* @type {NodeList|Array<Element>}
* @private
*/
this.mediaButtons_ = [];
}
/**
* This inherited method can be used to complete additional load processes before initializing the component.
* In this case the Swiper js library will be loaded. {@link https://swiperjs.com/swiper-api}
* @inheritDoc
*/
async waitFor()
{
await super.waitFor();
await libs.loadSwiper();
}
/**
* Component is ready and had loaded all dependencies (inherit method waitFor and sub components).
* @inheritDoc
*/
onInit()
{
super.onInit();
/**
* Dom element in which the (dynamically built) content is added.
* @type {Element}
* @private
*/
this.contentWrapper_ = this.getElement().querySelector('.layer-content-wrapper');
}
/**
* Takes care of changes triggered by the {@Link LayerEventType} `UPDATE_LAYER` event of the {@Link LayerProvider}
* @inheritDoc
*/
handleUpdateLayer_(event)
{
if(this.swiper_ && this.swiper_.slides.length > event.data['mediaId'])
this.swiper_.slideTo(event.data['mediaId']);
}
/**
* Dynamically creates the layer content/dom structure to display the swipeable media gallery.
* @inheritDoc
*/
async initContent_(layer, data)
{
// destroy old video player instances
let playerContainers = this.contentWrapper_.querySelectorAll('.layer-media-vimeo');
for(let j=0; j<playerContainers.length; j++)
this.videoProvider_.deletePlayer(playerContainers[j].id);
// destoy old swiper instances if exist
if(this.swiper_)
{
unlisten(this.swiper_.navigation.nextEl, EventType.CLICK, this.handleNextSwipeClick_, false, this);
unlisten(this.swiper_.navigation.prevEl, EventType.CLICK, this.handlePrevSwipeClick_, false, this);
this.swiper_.destroy();
this.swiper_ = null;
}
// remove the old content
if(this.content_)
this.content_.remove();
await super.initContent_(layer, data);
if(!this.layerData_)
throw new Error('Couldn\'t init artwork media with empty data!');
let elementModel = this.exhibitionProvider_.items[this.layerData_['id']];
this.artwork_ = elementModel ? elementModel.getElement(this.layerData_['elementId']) : null;
if(!this.artwork_)
throw new Error('Couldn\'t init media of artwork:"'+this.layerData_['elementId']+'"!');
await this.artwork_.loadInfos();
let info = this.artwork_.infos[this.layerData_['infoId']];
let swiperSlides = [];
for(let i = 0; i<info.media.length; i++)
{
let slide = createDom(TagName.DIV, {'class': 'swiper-slide'});
let mediaSource = info.media[i].src;
let mediaItemContent = [];
let mediaWrapperContent = [];
let isVideo = info.media[i].type == 'video';
let wrapperSize = new Size(0,0);
// creates an video element
if (isVideo)
{
let videoObject = await this.exhibitionProvider_.loadVideoData(mediaSource).then((obj) => obj);
mediaSource = videoObject && videoObject['thumbnail_url'] ? videoObject['thumbnail_url'] : 'images/video-thumb.png';
wrapperSize = new Size(videoObject['width'], videoObject['height']);
let playIcon = createDom(TagName.SPAN, {'class': 'layer-media-play-icon'});
mediaWrapperContent.push(playIcon);
let id = 'el' + i.toString() + '::' + info.media[i].src;
let mediaVideoBackground = createDom(TagName.DIV, {'class': 'layer-media-vimeo-background'});
dataset.set(mediaVideoBackground, 'cover', wrapperSize.width+'|'+wrapperSize.height);
let mediaVideoContainer = createDom(TagName.DIV, {'id': id, 'class': 'layer-media-vimeo'}, mediaVideoBackground);
dataset.set(mediaVideoContainer, 'vimeoId', info.media[i].src);
mediaItemContent.push(mediaVideoContainer);
}
// creates an image used as a video thumbnail image too
let media = createDom(TagName.IMG, {
'src': mediaSource,
'class': 'layer-media layer-media--' + info.media[i].type
});
if(wrapperSize.width > 0)
dataset.set(media, 'coverSize', wrapperSize.width+'|'+wrapperSize.height);
mediaWrapperContent.push(media);
media.setAttribute('draggable', false);
// creates button and wrapper elements
let mediaWrapper = createDom(isVideo ? TagName.BUTTON: TagName.DIV, {'class': 'layer-media-wrapper'+(isVideo ? ' media-button' : '')}, mediaWrapperContent);
if(wrapperSize.width > 0)
dataset.set(mediaWrapper, 'cover', wrapperSize.width+'|'+wrapperSize.height);
let mediaContainer = createDom(TagName.DIV, {'class': 'layer-media-container'}, mediaWrapper);
mediaItemContent.push(mediaContainer);
let mediaItem = createDom(TagName.DIV, {'class': 'layer-media-item layer-media-item--'+info.media[i].type}, mediaItemContent);
dataset.set(mediaItem, 'type', info.media[i].type);
let imageDescription = createDom(TagName.P, {'class': 'layer-media-description'}, info.media[i].description);
listenOnce(media, EventType.LOAD, (event) => {
let image = /** @type {Image} */ (event.target);
let coverSize = new Size(image.naturalWidth, image.naturalHeight);
if(dataset.has(image, 'coverSize'))
{
let sizeObj = dataset.get(image, 'coverSize').split('|');
coverSize = new Size(parseInt(sizeObj[0], 10), parseInt(sizeObj[1], 10));
dataset.remove(image, 'coverSize')
}
dataset.set(imageDescription, 'cover', coverSize.width+'|'+coverSize.height);
classlist.add(imageDescription, 'is-visible');
this.applyCoverLook_(imageDescription, coverSize);
}, false, this);
slide.append(mediaItem);
slide.append(imageDescription);
swiperSlides.push(slide);
}
let swiperWrapper = createDom(TagName.DIV, {'class': 'swiper-wrapper'}, swiperSlides);
let swiperContainer = createDom(TagName.DIV, {'class': 'swiper'}, swiperWrapper);
let swiperPagination = createDom(TagName.DIV, {'class': 'swiper-pagination swiper-pagination-fraction'});
let swiperNextButton = createDom(TagName.DIV, {'class': 'swiper-button-next layer-swiper-button'});
let swiperPrevButton = createDom(TagName.DIV, {'class': 'swiper-button-prev layer-swiper-button'});
this.content_ = createDom(TagName.DIV, {'class': 'layer-content is-visible'}, [swiperContainer, swiperPagination, swiperNextButton, swiperPrevButton]);
this.contentWrapper_.append(this.content_);
await Promise.resolve();
}
/**
* Initiate a video (autoplay) by clicking on the thumbnail image
* @param {Event} event
* @private
*/
handleMediaClick_(event)
{
let itemElement = getAncestorByClass(/** @type {Element} */ (event.currentTarget), 'layer-media-item');
let container = itemElement.querySelector('.layer-media-vimeo');
let vimeoId = dataset.get(container, 'vimeoId');
this.videoProvider_.createPlayer(container.id, container, {
'id': vimeoId,
'width': itemElement.offsetWidth,
'height': itemElement.offsetHeight,
'autoplay': true,
});
classlist.add(itemElement, 'is-playing');
classlist.add(itemElement, 'has-active-player');
setTimeout(() => {this.cursorProvider_.update();}, 0);
}
/**
* Activates all interactive elements in the active layer content (buttons, swiper).
* @inheritDoc
*/
activateContent_()
{
super.activateContent_();
this.mediaButtons_ = this.contentWrapper_.querySelectorAll('.media-button');
for(let i=0; i<this.mediaButtons_.length; i++)
listen(this.mediaButtons_[i], EventType.CLICK, this.handleMediaClick_, false, this);
let hasSlides = this.artwork_.infos[this.layerData_['infoId']].media.length > 1;
if(!this.swiper_)
{
this.swiper_ = new Swiper(this.content_.querySelector('.swiper'), {
'initialSlide': this.layerData_['mediaId'],
'pagination': {
'el': '.swiper-pagination',
'type': "fraction",
},
'navigation': {
'nextEl': '.swiper-button-next',
'prevEl': '.swiper-button-prev',
},
'on': {
'slideChange': () => {
if(this.swiper_)
{
this.autoResetProvider_.start();
let vimeoEl = this.swiper_.slides[this.swiper_.previousIndex].querySelector('.layer-media-vimeo');
if(vimeoEl)
{
let player = this.videoProvider_.getPlayer(vimeoEl.id);
if(player)
player.pause().catch((error) => {console.warn(error);});
}
}
}
}
});
}
if(hasSlides)
{
listen(this.swiper_.navigation.nextEl, EventType.CLICK, this.handleNextSwipeClick_, false, this);
listen(this.swiper_.navigation.prevEl, EventType.CLICK, this.handlePrevSwipeClick_, false, this);
this.swiper_.enable();
}
else
this.swiper_.disable();
this.handleResize_();
}
/**
* Deactivates all interactive elements in the active layer content (buttons, swiper, active videos).
* @inheritDoc
*/
deactivateContent_()
{
super.deactivateContent_();
if(this.swiper_) {
this.swiper_.disable();
unlisten(this.swiper_.navigation.nextEl, EventType.CLICK, this.handleNextSwipeClick_, false, this);
unlisten(this.swiper_.navigation.prevEl, EventType.CLICK, this.handlePrevSwipeClick_, false, this);
}
let playerContainers = this.contentWrapper_.querySelectorAll('.layer-media-vimeo');
for(let j=0; j<playerContainers.length; j++)
{
let player = this.videoProvider_.getPlayer(playerContainers[j].id);
if(player)
player.pause().catch((error) => {console.warn(error);});
}
for(let i=0; i<this.mediaButtons_.length; i++)
unlisten(this.mediaButtons_[i], EventType.CLICK, this.handleMediaClick_, false, this);
this.mediaButtons_ = [];
}
/**
* Handle click for swiping to next media item
* @private
*/
handleNextSwipeClick_()
{
if(this.swiper_.isEnd && !this.swiper_.animating)
this.swiper_.slideTo(0, Math.min(600, this.swiper_.slides.length * 200));
}
/**
* Handle click for swiping to previous media item
* @private
*/
handlePrevSwipeClick_()
{
if(this.swiper_.isBeginning && !this.swiper_.animating)
this.swiper_.slideTo(this.swiper_.slides.length - 1, Math.min(600, this.swiper_.slides.length * 200));
}
/**
* Checks whether a content change and thus an update of
* the content should take place when the layer is called up again.
* @inheritDoc
*/
checkLayerContentInitalisation_(event)
{
let isSameContent = this.layerData_ != null && event.data != null && this.layerData_['id'] == event.data['id'] && this.layerData_['elementId'] == event.data['elementId'] && this.layerData_['infoId'] == event.data['infoId'];
return !isSameContent;
}
/**
* Handle resizing of the window to update the functionality of custom cover element look.
* @inheritDoc
*/
handleResize_()
{
super.handleResize_();
if(this.swiper_)
{
let coverElements = this.content_.querySelectorAll('[data-cover]');
coverElements.forEach((coverElement) => {
let coverObj = /** @type {string} */ (dataset.get(/** @type {Element} */ (coverElement), 'cover')).split('|');
let coverSize = new Size(parseInt(coverObj[0], 10), parseInt(coverObj[1], 10));
this.applyCoverLook_(/** @type {!Element} */ (coverElement), coverSize);
});
}
}
/**
* Apply custom cover element look.
* @param {!Element} coverElement
* @param {!Size} coverSize
* @private
*/
applyCoverLook_(coverElement, coverSize)
{
let parentElement = coverElement.parentElement;
if(classlist.contains(coverElement, 'layer-media-description'))
parentElement = parentElement.querySelector('.layer-media-container');
let parentSize = new Size(parentElement.offsetWidth, parentElement.offsetHeight);
let newSize = new Size(
parentSize.width,
parentSize.width / coverSize.width * coverSize.height
);
if(newSize.height > parentSize.height)
newSize = new Size(
parentSize.height / coverSize.height * coverSize.width,
parentSize.height
);
if(classlist.contains(coverElement, 'layer-media-description'))
setStyle(coverElement, {'paddingLeft': Math.round((parentSize.width - newSize.width) * .5) + 'px'});
else
setStyle(coverElement, {'width': newSize.width+'px', 'height': newSize.height+'px'});
}
}
exports = MediaLayer;