goog.module('gep.components.ExhibitLayer');
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 {createDom,removeChildren} = 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,unlisten,EventType,Event} = goog.require('goog.events');
/**
* Component that controls the display of the exhibit layer and its contents.
* @extends {AbstractLayer}
*/
class ExhibitLayer extends AbstractLayer
{
constructor()
{
super(LayerType.EXHIBIT);
/**
* 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();
/**
* {@Link ExhibitionElementModel} which represents the content data of the currently opened or last opened layer.
* @type {ExhibitionElementModel}
* @private
*/
this.artwork_ = 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_ = [];
}
/**
* 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');
}
/**
* Dynamically creates the layer content/dom structure to display the artwork infos.
* @inheritDoc
*/
async initContent_(layer, data)
{
let playerContainers = this.contentWrapper_.querySelectorAll('.layer-media-vimeo');
for(let j=0; j<playerContainers.length; j++)
this.videoProvider_.deletePlayer(playerContainers[j].id);
removeChildren(/** @type {Node} */ (this.contentWrapper_));
await super.initContent_(layer, data);
if(!this.layerData_)
throw new Error('Couldn\'t init artwork with empty data!');
// find exhibition element model by the given extra data values: id = exhibit model id and element id = artwork model id
let itemModel = this.exhibitionProvider_.items[this.layerData_['id']];
this.artwork_ = itemModel ? itemModel.getElement(this.layerData_['elementId']) : null;
if(!this.artwork_)
throw new Error('Couldn\'t init artwork:"'+this.layerData_['elementId']+'"!');
await this.artwork_.loadInfos();
// check if the artwork info has mor than on info content and a submenu will be generated
let hasSubContents = this.artwork_.infos.length > 1;
if(hasSubContents)
{
let content = createDom(TagName.DIV, {'class': 'layer-content is-visible'});
dataset.set(content, 'content', 'overview');
this.contentWrapper_.append(content);
this.createCloseButton_(content);
let scrollWrapper = createDom(TagName.DIV, {'class': 'layer-content-scroll-wrapper is-inverted'});
dataset.set(scrollWrapper, 'cmp', 'minibar-container');
content.append(scrollWrapper);
this.artwork_.infos.forEach((info, index) => {
let icon = createDom(TagName.SPAN, {'class': 'icon'});
icon.innerHTML = '<svg viewBox="0 0 11 16" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.41421 0.707107C1.80474 0.316583 2.4379 0.316582 2.82843 0.707106L10.2426 8.12132L2.82843 15.5355C2.4379 15.9261 1.80474 15.9261 1.41421 15.5355L0.707107 14.8284C0.316582 14.4379 0.316583 13.8047 0.707107 13.4142L6 8.12132L0.707107 2.82843C0.316582 2.4379 0.316583 1.80474 0.707107 1.41421L1.41421 0.707107Z"/></svg>';
let label = createDom(TagName.SPAN, {'class': 'label'}, info.name);
let button = createDom(TagName.BUTTON, {'class': 'icon-button layer-content-switcher layer-content-artwork is-inverted', 'aria-label': 'show artwork details'}, [icon, label]);
dataset.set(button, 'content', 'artwork' + (index+1));
scrollWrapper.append(button);
});
}
// create artwork info
for(let i = 0; i<this.artwork_.infos.length; i++)
{
let info = this.artwork_.infos[i];
let content = createDom(TagName.DIV, {'class': 'layer-content'+(hasSubContents ? ' is-subcontent' : ' is-visible')});
dataset.set(content, 'content', 'artwork' + (i+1));
this.contentWrapper_.append(content);
// if a submenu will be generated create a back button else create a normal close button
if(hasSubContents)
this.createBackButton_(content);
else
this.createCloseButton_(content);
let scrollWrapper = createDom(TagName.DIV, {'class': 'layer-content-scroll-wrapper is-inverted'});
// create a minibar container sub component for custom scroll bars
dataset.set(scrollWrapper, 'cmp', 'minibar-container');
content.append(scrollWrapper);
let headline = createDom(TagName.H2, {'class': 'layer-title layer-artwork-title'}, info.name);
scrollWrapper.append(headline);
// create text info
info.details.forEach((detailText, detailTitle) => {
if(detailText.trim() != '')
{
let title = createDom(TagName.H3, {'class': 'layer-subtitle'}, detailTitle);
scrollWrapper.append(title);
let text = createDom(TagName.P, {'class': 'layer-text'});
text.innerHTML = detailText;
scrollWrapper.append(text);
}
});
// create media elements (image and video)
for(let j = 0; j<info.media.length; j++)
{
let mediaSource = info.media[j].src;
let mediaItemContent = [];
let mediaWrapperContent = [];
// creates an video element
if(info.media[j].type == 'video')
{
let videoObject = await this.exhibitionProvider_.loadVideoData(mediaSource).then((obj) => obj);
mediaSource = videoObject && videoObject['thumbnail_url'] ? videoObject['thumbnail_url'] : 'images/video-thumb.png';
let playIcon = createDom(TagName.SPAN, {'class': 'layer-media-play-icon'});
mediaWrapperContent.push(playIcon);
let id = 'el' + i.toString() + '::' + info.media[j].src;
let mediaVideoContainer = createDom(TagName.DIV, {'id': id, 'class': 'layer-media-vimeo'});
dataset.set(mediaVideoContainer, 'vimeoId', info.media[j].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[j].type});
media.setAttribute('draggable', false);
mediaWrapperContent.push(media);
// creates button and wrapper elements
let mediaWrapper = createDom(TagName.BUTTON, {'class': 'layer-media-wrapper media-button'}, mediaWrapperContent);
mediaItemContent.push(mediaWrapper);
let mediaZoom = createDom(TagName.BUTTON, {'class': 'layer-media-zoom media-button'});
mediaItemContent.push(mediaZoom);
let mediaSpacer = createDom(TagName.DIV, {'class': 'layer-media-spacer'});
mediaItemContent.push(mediaSpacer);
let mediaItem = createDom(TagName.DIV, {'class': 'layer-media-item'}, mediaItemContent);
dataset.set(mediaItem, 'type', info.media[j].type);
dataset.set(mediaItem, 'infoId', i.toString());
dataset.set(mediaItem, 'mediaId', j.toString());
scrollWrapper.append(mediaItem);
let imageDescription = createDom(TagName.P, {'class': 'layer-media-description is-visible'}, info.media[j].description);
scrollWrapper.append(imageDescription);
}
}
await this.manager.decorate(this.contentWrapper_).then(() => {
this.initContentElements_();
});
}
/**
* Activates all interactive elements in the active layer content (buttons).
* @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);
}
/**
* Deactivates all interactive elements in the active layer content (buttons, active videos).
* @inheritDoc
*/
deactivateContent_()
{
super.deactivateContent_();
this.layerProvider_.hide('media');
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_ = [];
}
/**
* Initiate a video (autoplay) by clicking on the thumbnail image
* @param {Event} event
* @private
*/
handleMediaClick_(event)
{
let button = /** @type {Element} */ (event.currentTarget);
let parentElement = button.parentElement;
let type = dataset.get(parentElement, 'type');
if(type == 'video' && !classlist.contains(button, 'layer-media-zoom'))
{
let container = parentElement.querySelector('.layer-media-vimeo');
let vimeoId = dataset.get(container, 'vimeoId');
this.videoProvider_.createPlayer(container.id, container, {
'id': vimeoId,
'width': parentElement.offsetWidth,
'autoplay': true
});
classlist.add(parentElement, 'is-playing');
classlist.add(parentElement, 'has-active-player');
setTimeout(() => {this.cursorProvider_.update();}, 0);
}
else
{
let infoId = parseInt(dataset.get(parentElement, 'infoId'), 10);
let mediaId = parseInt(dataset.get(parentElement, 'mediaId'), 10);
this.layerProvider_.show('media', {'id': this.layerData_['id'], 'elementId': this.artwork_.id.toString(), 'infoId': infoId, 'mediaId': mediaId})
}
}
/**
* Creates a layer close button dom element
* @param {Element} container
* @private
*/
createCloseButton_(container)
{
let button = createDom(TagName.BUTTON, {'class': 'layer-close is-inverted', 'aria-label': 'close exhibit info'});
button.innerHTML = '<svg class="icon" width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="19.3047" y="21.3344" width="27" height="3" rx="1" transform="rotate(-135 19.3047 21.3344)"/><rect x="21.2129" y="2.12132" width="27" height="3" rx="1" transform="rotate(135 21.2129 2.12132)"/></svg>';
container.append(button);
}
/**
* Creates a layer back button dom element
* @param {Element} container
* @private
*/
createBackButton_(container)
{
let button = createDom(TagName.BUTTON, {'class': 'icon-button layer-content-switcher is-inverted layer-content-back', 'aria-label': 'hide artwork details'});
button.innerHTML = '<span class="icon"><svg width="24px" height="14px" xmlns="http://www.w3.org/2000/svg"><path d="M23,6.5H2.207l5.146-5.146c0.195-0.195,0.195-0.512,0-0.707s-0.512-0.195-0.707,0l-6,6C0.601,6.692,0.564,6.748,0.539,6.809c-0.051,0.122-0.051,0.26,0,0.382c0.025,0.062,0.062,0.117,0.108,0.163l6,6C6.744,13.451,6.872,13.5,7,13.5s0.256-0.049,0.354-0.146c0.195-0.195,0.195-0.512,0-0.707L2.207,7.5H23c0.276,0,0.5-0.224,0.5-0.5S23.276,6.5,23,6.5z"></path></svg></span>';
dataset.set(button, 'content', 'overview');
container.append(button);
}
/**
* 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'];
return !isSameContent;
}
}
exports = ExhibitLayer;