class Gallery { constructor(modal, config) { this.imageWrapper = config.imageWrapper??null; this.container = config.container ? document.querySelector(config.container) : document.querySelector('main'); this.modal = new window.jvbModal( modal, { onOpen: false, onSave: false, onClose: () => this.closeGallery(false) } ); this.items = this.getGalleryItems() || []; this.loadMore = config.loadMore??false; this.modalElement = (typeof modal === 'string') ? document.querySelector(modal)??false : modal; if (!this.modal) { return; } this.a11y = window.jvbA11y; this.openWhenReady = false; this.initElements(); this.imageWrapper = (typeof this.imageWrapper === 'string') ? this.imageWrapper : '.'+this.imageWrapper.classList.join('.'); this.index = 0; this.swipe = { touchStart: null, touchEnd: null, minSwipe: 50, }; this.isLoading = false; } initElements() { this.prevBtn = this.modalElement.querySelector('.prev'); this.nextBtn = this.modalElement.querySelector('.next'); this.favourite = this.modalElement.querySelector('.favourite'); this.image = this.modalElement.querySelector('.image'); this.counter = this.modalElement.querySelector('.counter'); this.extra = this.modalElement.querySelector('.item-info'); //If we don't have the wrapper set up, we can remove these elements if (!this.imageWrapper) { this.favourite.remove(); this.extra.remove(); } } getGalleryItems() { let search = (this.imageWrapper) ? this.imageWrapper : 'img'; return Array.from(this.container.querySelectorAll(search)) .map(item => { const img = (this.imageWrapper) ? item.querySelector('img') : item; if (!img) return null; return { id: (this.imageWrapper) ? item.querySelector('button.favourite').dataset.id : '', small: img.dataset.small || img.src, large: img.dataset.large || img.src, full: img.dataset.full || img.src, alt: img.alt || '', fav: (this.imageWrapper) ? item.querySelector('button.favourite')?.cloneNode(true) : '', info: (this.imageWrapper) ? item.querySelector('.item-info')?.cloneNode(true) : '' }; }).filter(Boolean); } initListeners() { this.container.addEventListener('click', this.handleClick.bind(this)); document.addEventListener('keydown', this.handleKeys.bind(this)); document.addEventListener('touchstart', this.handleTouchStart.bind(this)); document.addEventListener('touchend', this.handleTouchEnd.bind(this)); document.addEventListener('touchmove', this.handleTouchMove.bind(this)); } destroyListeners() { this.container.removeEventListener('click', this.handleClick.bind(this)); document.removeEventListener('keydown', this.handleKeys.bind(this)); document.removeEventListener('touchstart', this.handleTouchStart.bind(this)); document.removeEventListener('touchend', this.handleTouchEnd.bind(this)); document.removeEventListener('touchmove', this.handleTouchMove.bind(this)); } handleKeys(e) { switch (e.key) { case 'ArrowLeft': this.navigate(-1); break; case 'ArrowRight': this.navigate(1); break; } } handleTouchStart(e) { this.swipe.touchStart = e.touches[0].clientX; } handleTouchMove(e) { this.swipe.touchEnd = e.touches[0].clientX; } handleTouchEnd(e) { if (!this.swipe.touchStart || !this.swipe.touchEnd) return; const distance = this.swipe.touchStart - this.swipe.touchEnd; const isLeftSwipe = distance > this.swipe.minSwipe; const isRightSwipe = distance < -this.swipe.minSwipe; if (isLeftSwipe) { this.navigate(1); } else if (isRightSwipe) { this.navigate(-1); } this.swipe.touchStart = null; this.swipe.touchEnd = null; } handleClick(e) { //test if it's a link click, and the click has an image if(window.targetCheck(e, '.feed-images')){ this.handleGalleryOpen(e); } if(window.targetCheck(e, '.nav')) { if (window.targetCheck(e, '.next')) { this.navigate(1); } else { this.navigate(-1); } } if (window.targetCheck(e, 'button.cancel')) { this.closeGallery(); } } async navigate(direction) { let newIndex = this.index + direction; //Check if out of bounds if (newIndex <0 || newIndex >= this.items.length) { this.a11y.announceNavigation(newIndex, this.items.length, direction < 0, direction > 0); if (newIndex <0) { newIndex = this.items.length - 1; } else { newIndex = 0; } } //update index this.index = newIndex; this.updateDisplay(); this.preloadImages(); this.a11y.announceNavigation(this.index, this.items.length); if (typeof this.loadMore === 'function' && direction > 0 && newIndex >= (this.items.length - 3)) { if (window.feedBlock.hasMore) { await this.loadMore(); this.updateGalleryItems(this.getGalleryItems()); } } } updateGalleryItems(newItems) { const currentItem = this.items[this.index]; this.items = newItems; if (currentItem) { const newIndex = this.items.findIndex(item => item.full === currentItem.full || item.large === currentItem.large ); if (newIndex !== -1) { this.index = newIndex; } } this.updateNavigationButtons(); if(this.items.length > 0 && this.openWhenReady) { let index = this.findIndex('id', this.openWhenReady); this.openGallery(index); } } preloadImages() { [-1,0,1].forEach(offset => { const index = this.index + offset; if (index >=0 && index < this.items.length) { const img = new Image(); const item = this.items[index]; if (window.innerWidth < 1000) { img.src = item.large || item.src; } else { img.src = item.full || item.src; } } }); } updateDisplay() { const item = this.items[this.index]; if (!item) return; [ this.image.src, this.image.alt, this.counter.textContent ] = [ (window.innerWidth < 1000) ? (item.large || item.src) : (item.full || item.src), item.alt || '', `${this.index + 1} / ${this.items.length}` ]; if (this.imageWrapper) { if (item.fav) { window.removeChildren(this.favourite); this.favourite.appendChild(item.fav.cloneNode(true)); } if (item.info) { window.removeChildren(this.extra); this.extra.appendChild(item.info.cloneNode(true)); } } this.updateNavigationButtons(); } updateNavigationButtons() { this.prevBtn.classList.toggle('end', this.index > 0 ? '' : 'none'); this.nextBtn.classList.toggle('end', this.index < this.items.length -1 ? '' : 'none'); } handleGalleryOpen(e) { let item = (this.imageWrapper) ? e.target.closest(this.imageWrapper) : e.target.closest('img'); let key = (this.imageWrapper) ? 'id' : 'small'; let value = item.dataset[key]; let index = this.findIndex(key, value); this.openGallery(index); } findIndex(property, value) { return this.items.findIndex(obj => obj[property] === value); } openGallery(index) { this.initListeners(); this.index = index; this.modal.handleOpen(); this.updateDisplay(); this.preloadImages(); this.a11y.announce(`Image ${this.index + 1} of ${this.items.length}. Use arrow keys to navigate.`); } closeGallery(useModal = true) { this.destroyListeners(); if (useModal) { this.modal.handleClose(); } } } window.jvbGallery = Gallery;