Source: media/lightbox/Lightbox.js

  1. import { PopupPagination } from "../../map/popup/PopupPagination.js";
  2. import { MediaCaption } from "./utils.js";
  3. import { CloseButton, ShareButton, MetadataButton, ZoomText } from "./uiElements.js"
  4. import { EventObject } from "../../events/EventObject.js";
  5. import { DeviceProperties, ElementProperties } from "../utils/helperTools.js";
  6. import { PhotoswipeLightbox } from "./PhotoswipeLightbox.js";
  7. import { PhotoswipeLightboxEvents } from "./PhotoswipeLightboxEvents.js";
  8. export { Lightbox }
  9. /**
  10. * Lightbox class to build a media lightbox using the core functionalities of the photoswipe lightbox. However, the pswp UI is replaced by own UI design, thus own DOM structure and elements (e.g., buttons, text).
  11. * @extends EventObject
  12. * @author ckraemme <ckraemme@awi.de>
  13. */
  14. class Lightbox extends EventObject {
  15. /**
  16. * Creates the lightbox.
  17. * @param {Object} lightboxData - LightboxData class object.
  18. * @param {Object} lightboxConfig - LightboxConfig class object.
  19. */
  20. constructor(lightboxData, lightboxConfig) {
  21. super({
  22. "lightbox_open": [], // [1]
  23. "lightbox_close": [],// [1]
  24. "lightbox_critical_state": [],
  25. //[1]: required for enabling/disabling scrollbars
  26. });
  27. this.lightboxData = lightboxData;
  28. this.lightboxConfig = lightboxConfig;
  29. }
  30. init() {
  31. if (this.lightboxConfig.slideOnStartIndex != null) {
  32. this._build();
  33. }
  34. const gallery = this.lightboxConfig.targetElement.querySelector('.pswp-gallery');
  35. if (gallery) {
  36. gallery.addEventListener("click", (e) => {
  37. this.lightboxConfig.slideOnStartIndex = this._getIndexOfItemInGallery(e.target);
  38. if (Number.isInteger(this.lightboxConfig.slideOnStartIndex)) {
  39. e.preventDefault();
  40. this._build();
  41. }
  42. })
  43. }
  44. }
  45. _opening() {
  46. this.lightboxWrapper.classList.remove('state-before-opening');
  47. this.lightboxWrapper.classList.add('state-opening');
  48. this._initLightboxHistoryEntry();
  49. }
  50. _open() {
  51. this.lightboxWrapper.classList.remove('state-opening');
  52. }
  53. _closing() {
  54. this.lightboxWrapper.classList.add('state-closing');
  55. this.shareButton.closeContent();
  56. this._disableClosingAnimationBasedOnItemVisibility();
  57. this._removeLightboxHistoryEntry();
  58. }
  59. _closed() {
  60. this.lightboxWrapper.remove();
  61. this.pswpLightbox = null;
  62. this.lightboxWrapper.classList.remove('state-closing');
  63. this.lightboxWrapper = null;
  64. }
  65. /**
  66. * FixMe: This feature collides with the VUE routing in the portal. Somehow a page reload is triggered when
  67. * history.back is called.
  68. *
  69. * On touch screens, allow the user to close the lightbox using the browser's back button (i.e. swipe back).
  70. * @private
  71. */
  72. _initLightboxHistoryEntry() {
  73. //if (DeviceProperties.isTouchDevice()) {
  74. // if (history.state?.name != "Lightbox")
  75. // history.pushState({ name: "Lightbox" }, null, document.URL);
  76. //
  77. // this.historyStateMethod = this._removeLightboxHistoryEntry.bind(this);
  78. // window.addEventListener('popstate', this.historyStateMethod);
  79. //}
  80. }
  81. _removeLightboxHistoryEntry() {
  82. //if (DeviceProperties.isTouchDevice()) {
  83. // window.removeEventListener('popstate', this.historyStateMethod);
  84. // if (history.state?.name == "Lightbox")
  85. // history.back();
  86. // this.pswpLightbox.lightbox.pswp?.close();
  87. //}
  88. }
  89. /**
  90. * Disable lightbox closing animation if the gallery item is not in the viewport.
  91. * @private
  92. */
  93. _disableClosingAnimationBasedOnItemVisibility() {
  94. const index = this.pswpLightbox.lightbox.pswp.currIndex;
  95. const placeholderElement = document.querySelectorAll('.pswp-gallery__item')[index];
  96. const deviation = 1.0;
  97. const isInViewport = ElementProperties.isInViewport(placeholderElement, deviation);
  98. if (!isInViewport)
  99. this.pswpLightbox.lightbox.pswp.options.showHideAnimationType = 'none';
  100. }
  101. _getIndexOfItemInGallery(itemNode) {
  102. const galleryItemNodes = document.querySelectorAll('.pswp-gallery__item');
  103. let itemIndex;
  104. for (let index = 0; index < galleryItemNodes.length; index++) {
  105. const item = galleryItemNodes[index];
  106. if (item.contains(itemNode)) {
  107. itemIndex = index;
  108. break
  109. }
  110. }
  111. return itemIndex
  112. }
  113. /**
  114. * Set the height of the lightbox to window.innerHeight to prevent overflowing
  115. * content below the address bar in mobile browsers.
  116. * @private
  117. */
  118. _setLightboxResizeListener() {
  119. this._resetLightboxResizeListener();
  120. this._resizeListener = () => {
  121. this.lightboxWrapper.style.height = window.innerHeight + "px";
  122. };
  123. window.addEventListener("resize", this._resizeListener);
  124. this._resizeListener();
  125. }
  126. _resetLightboxResizeListener() {
  127. if (this._resizeListener) {
  128. window.removeEventListener("resize", this._resizeListener);
  129. }
  130. }
  131. _build() {
  132. //Observe: 0 to slideOnStartIndex is required so all items before current item are filtered, thus slideOnStartIndex matches with filtered grid
  133. this.lightboxData.loadItems(0, this.lightboxConfig.slideOnStartIndex || 1, false, {})
  134. //this.lightboxData.getItemsQuantity() //TODO: why is it not working?
  135. //this.lightboxData.getItem(this.lightboxConfig.slideOnStartIndex, [-3, 3]) //replaces: options.preloadFirstSlide=true //OR put in 'itemData' and activate preloadFirstSlide?
  136. .then(() => {
  137. this._initDOM();
  138. this.pswpLightbox = new PhotoswipeLightbox(this.lightboxConfig.targetElement.querySelector('.pswp-lightbox'), this.lightboxData, this.lightboxConfig.slideOnStartIndex);
  139. this._handlePswpEvents();
  140. this._setLightboxResizeListener();
  141. this.fire("lightbox_open");
  142. }).catch((error) => {
  143. this.fire("lightbox_critical_state", error)
  144. })
  145. }
  146. _initDOM() {
  147. this.lightboxWrapper = document.createElement('div');
  148. this.lightboxWrapper.classList.add(...['vef-ui', 'custom-lightbox', 'state-before-opening']);
  149. this._initDOMHeader();
  150. this._initDOMContent();
  151. this._initDOMPagination();
  152. this.lightboxConfig.targetElement.append(this.lightboxWrapper);
  153. }
  154. _initDOMHeader() {
  155. let lightboxHeader = document.createElement('div');
  156. lightboxHeader.className = 'vef-ui lightbox-header';
  157. const headerLeft = document.createElement('div');
  158. headerLeft.classList.add('header-left');
  159. this.mobileMetadataButton = new MetadataButton(headerLeft);
  160. const headerRight = document.createElement('div');
  161. headerRight.classList.add('header-right');
  162. this.zoomText = new ZoomText(headerRight);
  163. this.shareButton = new ShareButton(headerRight);
  164. this.closeButton = new CloseButton(headerRight);
  165. if (this.lightboxConfig.showShareButton === false)
  166. this.shareButton.hideButton()
  167. lightboxHeader.append(headerLeft);
  168. lightboxHeader.append(headerRight);
  169. this.lightboxWrapper.prepend(lightboxHeader);
  170. }
  171. _initDOMContent() {
  172. const pswpLightbox = document.createElement('div');
  173. pswpLightbox.classList.add(...['vef-ui', 'lightbox-content', 'pswp-lightbox']);
  174. this.lightboxWrapper.append(pswpLightbox);
  175. this.shareButton.createBox(pswpLightbox);
  176. PhotoswipeLightboxEvents.preventOnShareBox(this.shareButton.contentBoxElement);
  177. }
  178. _initDOMPagination() {
  179. const lightboxFooter = document.createElement('div');
  180. lightboxFooter.classList.add(...['vef-ui', 'lightbox-footer']);
  181. this.pagination = new PopupPagination(lightboxFooter, { pageCount: this.lightboxData.dataItemAmount });
  182. this.lightboxWrapper.append(lightboxFooter);
  183. }
  184. _handlePswpEvents() {
  185. this.pswpLightbox.on('pswp_lightbox_opening', () => {
  186. this._opening();
  187. })
  188. this.pswpLightbox.on('pswp_lightbox_open', () => {
  189. this._open();
  190. })
  191. this.pswpLightbox.on('pswp_lightbox_ready', () => {
  192. const lightbox = this.pswpLightbox.lightbox;
  193. this.pagination.on('next', () => { lightbox.pswp.next() });
  194. this.pagination.on('previous', () => { lightbox.pswp.prev() });
  195. this.closeButton.onClick(() => { lightbox.pswp.close() })
  196. })
  197. this.pswpLightbox.on("pswp_lightbox_slide_change", (index) => {
  198. //console.log(`slide change [${index + 1}/${this.lightboxData.galleryItemAmount}]`);
  199. if (this.lightboxConfig.showShareButton === true) {
  200. const shareLink = this.lightboxData.getShareLink(index)
  201. this.shareButton.setLink(shareLink);
  202. const deactivateShareButton = shareLink == undefined;
  203. deactivateShareButton ? this.shareButton.deactivateButton() : this.shareButton.activateButton();
  204. }
  205. this.pagination.setPage(this.lightboxData.shownItemPosition(index));
  206. this.lightboxData.getSidebarTemplate().then((metadataTemplate) => {
  207. const isItemLoaded = this.lightboxData['galleryItems'][index] != null;
  208. if (isItemLoaded) {
  209. const caption = MediaCaption.getCaption(this.lightboxData['galleryItems'][index]['sidebarData'], metadataTemplate);
  210. const captionTarget = document.querySelector('.lightbox-content');
  211. this.mobileMetadataButton.setContent(caption, captionTarget);
  212. }
  213. })
  214. })
  215. this.pswpLightbox.on("pswp_lightbox_zoom", ([zoomLevel, showZoomLevel]) => {
  216. this.zoomText.isContentVisible = showZoomLevel;
  217. this.zoomText.setZoom(zoomLevel);
  218. })
  219. this.pswpLightbox.on('pswp_lightbox_close', () => {
  220. this.fire("lightbox_close");
  221. this._resetLightboxResizeListener();
  222. this._closing();
  223. })
  224. this.pswpLightbox.on('pswp_lightbox_destroy', () => {
  225. this._closed();
  226. })
  227. this.pswpLightbox.on('pswp_critical_state', (message) => {
  228. this.fire("lightbox_critical_state", message);
  229. })
  230. }
  231. }