diff --git a/ui/media/js/image-modal.js b/ui/media/js/image-modal.js index 498c5f24..367c754c 100644 --- a/ui/media/js/image-modal.js +++ b/ui/media/js/image-modal.js @@ -1,6 +1,28 @@ "use strict" +/** + * @typedef {object} ImageModalRequest + * @property {string} src + * @property {ImageModalRequest | () => ImageModalRequest | undefined} previous + * @property {ImageModalRequest | () => ImageModalRequest | undefined} next + */ + +/** + * @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}} + */ const imageModal = (function() { + const backElem = createElement( + 'i', + undefined, + ['fa-solid', 'fa-arrow-left', 'tertiaryButton'], + ) + + const forwardElem = createElement( + 'i', + undefined, + ['fa-solid', 'fa-arrow-right', 'tertiaryButton'], + ) + const zoomElem = createElement( 'i', undefined, @@ -13,7 +35,7 @@ const imageModal = (function() { ['fa-solid', 'fa-xmark', 'tertiaryButton'], ) - const menuBarElem = createElement('div', undefined, 'menu-bar', [zoomElem, closeElem]) + const menuBarElem = createElement('div', undefined, 'menu-bar', [backElem, forwardElem, zoomElem, closeElem]) const imageContainer = createElement('div', undefined, 'image-wrapper') @@ -63,15 +85,87 @@ const imageModal = (function() { () => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')), ) - const close = () => { + const state = { + previous: undefined, + next: undefined, + } + + const clear = () => { imageContainer.innerHTML = '' + + Object.keys(state).forEach(key => delete state[key]) + } + + const close = () => { + clear() modalElem.classList.remove('active') document.body.style.overflow = 'initial' } - window.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && modalElem.classList.contains('active')) { + /** + * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory + */ + function init(optionsFactory) { + if (!optionsFactory) { close() + return + } + + clear() + + const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory + const src = typeof options === 'string' ? options : options.src + + const imgElem = createElement('img', { src }, 'natural-zoom') + imageContainer.appendChild(imgElem) + modalElem.classList.add('active') + document.body.style.overflow = 'hidden' + setZoomLevel(false) + + if (typeof options === 'object' && options.previous) { + state.previous = options.previous + backElem.style.display = 'unset' + } else { + backElem.style.display = 'none' + } + + if (typeof options === 'object' && options.next) { + state.next = options.next + forwardElem.style.display = 'unset' + } else { + forwardElem.style.display = 'none' + } + } + + const back = () => { + if (state.previous) { + init(state.previous) + } else { + backElem.style.display = 'none' + } + } + + const forward = () => { + if (state.next) { + init(state.next) + } else { + forwardElem.style.display = 'none' + } + } + + window.addEventListener('keydown', (e) => { + if (modalElem.classList.contains('active')) { + switch (e.key) { + case 'Escape': + close() + break + case 'ArrowLeft': + back() + break + case 'ArrowRight': + forward() + break + } } }) window.addEventListener('click', (e) => { @@ -86,15 +180,12 @@ const imageModal = (function() { } }) - return (optionsFactory) => { - const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory - const src = typeof options === 'string' ? options : options.src + backElem.addEventListener('click', back) - // TODO center it if < window size - const imgElem = createElement('img', { src }, 'natural-zoom') - imageContainer.appendChild(imgElem) - modalElem.classList.add('active') - document.body.style.overflow = 'hidden' - setZoomLevel(false) - } + forwardElem.addEventListener('click', forward) + + /** + * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory + */ + return (optionsFactory) => init(optionsFactory) })() diff --git a/ui/media/js/main.js b/ui/media/js/main.js index e9f3f9d8..f26730af 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -398,7 +398,30 @@ function showImages(reqBody, res, outputContainer, livePreview) { if ('seed' in result && !imageElem.hasAttribute('data-seed')) { const imageExpandBtn = imageItemElem.querySelector('.imgExpandBtn') imageExpandBtn.addEventListener('click', function() { - imageModal(imageElem.src) + function previousImage(img) { + const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const index = allImages.indexOf(img) + return allImages.slice(0, index).reverse()[0] + } + + function nextImage(img) { + const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem img')) + const index = allImages.indexOf(img) + return allImages.slice(index + 1)[0] + } + + function imageModalParameter(img) { + const previousImg = previousImage(img) + const nextImg = nextImage(img) + + return { + src: img.src, + previous: previousImg ? () => imageModalParameter(previousImg) : undefined, + next: nextImg ? () => imageModalParameter(nextImg) : undefined, + } + } + + imageModal(imageModalParameter(imageElem)) }) const req = Object.assign({}, reqBody, {