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;
|