import { isEqual } from "lodash";
// external dependencies
import _$ from "justifiedGallery";
import "justifiedGallery/dist/css/justifiedGallery.css"
// factory for jQuery Plugin
const $ = _$();
import { MediaGallery } from "../grid/mediaGallery.js";
import { LightboxData } from "../data/LightboxData.js";
import { LightboxConfig } from "../lightbox/LightboxConfig.js";
import { GalleryConfigLoader } from "../data/GalleryConfigLoader.js";
import { GalleryOverview } from "../overview/mediaGalleryOverview.js";
import { resolveTemplate } from "../../utils/template/resolveTemplate.js";
import { MediaGalleryService } from "../grid/utils.js";
import { GalleryTimeLineFilter } from "../filter/plot/GalleryTimeLineFilter.js";
import { GalleryMappingScheme } from "../data/utils.js";
import { Lightbox } from "../lightbox/Lightbox.js";
export { Gallery }
/**
* Class representing a media gallery.
* @author ckraemme <ckraemme@awi.de>
*/
class Gallery {
/**
* Initialize the gallery.
* @param {Element} containerNode - Target HTML container node.
*/
constructor(containerNode) {
this.container_ = containerNode;
this.gallery_ = null;
this.galleryOverview_ = null;
this.allowEmptyGallery = false;
document.documentElement.classList.add("prevent-scrollbar-reflow");
}
/**
* Update or initialize the gallery.
*/
update() {
this._resetUi();
this._buildGallery();
// FixMe: HideOnSite does not work anymore. Workaround using global variable
window.vefGalleryVisible = true;
}
/**
* Remove the gallery.
*/
dispose() {
this._resetUi();
document.documentElement.classList.remove("prevent-scrollbar-reflow");
// FixMe: HideOnSite does not work anymore. Workaround using global variable
window.vefGalleryVisible = false;
}
/**
* Tasks that are executed when lightbox is on open state.
* @private
*/
_onOpenLightbox() {
//Note: Prevent the document scroll bar from being displayed when the lightbox is open.
document.documentElement.classList.add("disable-scroll");
}
/**
* Tasks that are executed when lightbox is on close state.
* @private
*/
_onCloseLightbox() {
document.documentElement.classList.remove("disable-scroll");
}
/**
* Display error message in gallery UI and show stacktrace in console.
* @param {Error} error - Error object.
* @private
*/
_showError(error) {
const _truncateText = (str, limit) => {
const regex = new RegExp(`((?:.|\n){${limit}})(?:.|\n)(?:.|\n)+`, "g");
return str.replace(regex, "$1...")
}
console.error(error);
const message = error.message || error || '';
this._resetUi();
const template = resolveTemplate($("#gallery-error-template").html(), {
message: _truncateText(message, 100)
});
this.container_.innerHTML = template;
}
/**
* Tasks that are executed when the user interface is being reset.
* @private
*/
_resetUi() {
if (this.gallery_) this.gallery_.dispose();
if (this.galleryOverview_) this.galleryOverview_.dispose();
this._onCloseLightbox();
this.container_.innerHTML = "";
}
/**
* Build the gallery.
* @private
*/
_buildGallery() {
this.configuration = new GalleryConfigLoader();
this.configuration.loadConfigData().then(() => {
console.log(this.configuration);
const shareID = this.configuration.queryConfigDict['shareID'];
this._buildGalleryHeader();
this._buildGalleryMain(shareID);
}).catch((error) => {
this._showError(error)
});
}
/**
* Build the gallery header area.
* @private
*/
_buildGalleryHeader() {
this.container_.innerHTML = `<div class="gallery-header"></div>`;
const headerNode = this.container_.querySelector(".gallery-header");
this.galleryOverview_ = new GalleryOverview(headerNode, this.configuration);
this.galleryOverview_.on('gallery_overview_critical_state', message => this._showError(message));
this._buildTimeLineFilter(headerNode);
}
/**
* Build the gallery main area.
* @private
* @param {*} shareID - Image identifier for sharing.
* @param {{}} [filterConfig={}] - Filter configuration, i.e., to set a constant remote data filter.
*/
_buildGalleryMain(shareID, filterConfig = {}) {
const _applyAbstractFilter = (data, filterConfig) => {
const abstractFilter = filterConfig.remoteFilter?.abstractFilter;
if (abstractFilter) {
const pointer = data.galleryRemoteFilter.abstractFilterObject;
pointer.addFilter(pointer.cqlFilter, abstractFilter)
}
}
this._initGalleryData().then((galleryData) => {
_applyAbstractFilter(galleryData, filterConfig);
this.gallery_ = new MediaGallery(this.container_, galleryData, this.allowEmptyGallery);
this.gallery_.on('gallery_critical_state', message => this._showError(message));
_applyAbstractFilter(galleryData, filterConfig);
MediaGalleryService.getSharedSlideNumber(this.configuration, shareID).then((slideOnStartIndex) => {
const lightboxConfig = new LightboxConfig({
'targetElement': this.container_,
'slideOnStartIndex': slideOnStartIndex,
'showShareButton': this.configuration.galleryConfigDict['lightbox']?.['shareButton']?.['enable']
})
const lightbox = new Lightbox(galleryData, lightboxConfig);
lightbox.on("lightbox_open", () => this._onOpenLightbox());
lightbox.on("lightbox_close", () => this._onCloseLightbox());
lightbox.on("lightbox_critical_state", (message) => this._showError(message));
lightbox.init();
})
})
}
/**
* Update the gallery main area.
* @param {Object} eventData - Plotly's event data object that contains a data selection, i.e., from timeline filter.
* @private
*/
_updateGalleryMain(eventData) {
const isFiredWithoutInfo = eventData === undefined;
const isNewGalleryData = !isEqual(eventData, this.plotlyEventDataOld);
if (isFiredWithoutInfo || !isNewGalleryData)
return
let rangeFilterItem = {};
this.plotlyEventDataOld = eventData;
const rangeFilterValues = [];
eventData?.selections.forEach(function (selection) {
const missingUTCPattern = /^\d{0,4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}(?:\.\d+)$/;
const utcChar = missingUTCPattern.test(selection.x0) ? 'Z' : '';
var [time0, time1] = [new Date(selection.x0 + utcChar), new Date(selection.x1 + utcChar)];
const isInvertedOrder = (time1 - time0) < 0;
if (isInvertedOrder)
rangeFilterValues.push(["bt", time1.toISOString(), time0.toISOString()])
else
rangeFilterValues.push(["bt", time0.toISOString(), time1.toISOString()])
})
let filterConfig;
if (rangeFilterValues.length != 0) {
rangeFilterItem = {
column: GalleryMappingScheme.findPropertyKeyInMapping(this.configuration.galleryConfigDict, ['dateTime'])[0],
type: "time",
values: rangeFilterValues
}
const abstractRangeFilter = [rangeFilterItem];
filterConfig = {
'remoteFilter': {
'abstractFilter': abstractRangeFilter
}
};
}
this.gallery_.removeListeners();
if (this.gallery_) this.gallery_.dispose();
this.allowEmptyGallery = true;
this._buildGalleryMain(null, filterConfig);
}
/**
* Build the timeline filter.
* @param {Element} targetElement - Target HTML element.
* @private
*/
_buildTimeLineFilter(targetElement) {
const activateTimeLineFilter = this.configuration.galleryConfigDict.filters?.timeLineFilter?.enable;
if (activateTimeLineFilter) {
const timeLineFilter = new GalleryTimeLineFilter(targetElement, this.configuration);
timeLineFilter.createPlot(this._updateGalleryMain.bind(this));
timeLineFilter.on('plot_filter_critical_state', message => this._showError(message));
}
}
/**
* Init the data object for the media gallery grid and lightbox.
* @private
* @returns Gallery data object.
*/
_initGalleryData() {
const contentData = new LightboxData(this.configuration.galleryConfigDict);
window.galleryData = contentData;
return contentData.initDataService(this.configuration.serviceConfigDict).then(() => contentData)
}
}