|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
/*! ILA UI Elements
|
|
|
|
|
*Copyright (C) 2021-2022 Aonghus Storey
|
|
|
|
|
*Copyright (C) 2021–2023 Aonghus Storey
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
@ -67,6 +67,96 @@ function makeButton(name, css = "", text = "", title = "", icon = "", handler =
|
|
|
|
|
return el;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Adds custom swipe events to a given element.
|
|
|
|
|
*
|
|
|
|
|
* When instantiated, a `swiped-[DIRECTION]` event will be dispatched when that element is swiped.
|
|
|
|
|
* A `swiped` event is also dispatched with the direction in the customEvent.detail, to allow for a single listener if needed.
|
|
|
|
|
*
|
|
|
|
|
* @public
|
|
|
|
|
* @param {HTMLElement} el The element on which to listen for swipe events.
|
|
|
|
|
* @fires Swipe#swiped
|
|
|
|
|
* @fires Swipe#swiped-up
|
|
|
|
|
* @fires Swipe#swiped-down
|
|
|
|
|
* @fires Swipe#swiped-left
|
|
|
|
|
* @fires Swipe#swiped-right
|
|
|
|
|
*/
|
|
|
|
|
class Swipe {
|
|
|
|
|
|
|
|
|
|
constructor(el) {
|
|
|
|
|
this._el = el;
|
|
|
|
|
el.addEventListener('touchstart',
|
|
|
|
|
e => {
|
|
|
|
|
this.startX = e.changedTouches[0].clientX;
|
|
|
|
|
this.startY = e.changedTouches[0].clientY;
|
|
|
|
|
});
|
|
|
|
|
el.addEventListener('touchend',
|
|
|
|
|
e => {
|
|
|
|
|
this.endX = e.changedTouches[0].clientX;
|
|
|
|
|
this.endY = e.changedTouches[0].clientY;
|
|
|
|
|
this._sendEvents();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event dispatched by any swipe.
|
|
|
|
|
* @public
|
|
|
|
|
* @event Swipe#swiped
|
|
|
|
|
* @type {object}
|
|
|
|
|
* @property {object} detail
|
|
|
|
|
* @property {string} detail.direction - The direction of the swipe action.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event dispatched on swipe up.
|
|
|
|
|
* @public
|
|
|
|
|
* @event Swipe#swiped-up
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event dispatched on swipe down.
|
|
|
|
|
* @public
|
|
|
|
|
* @event Swipe#swiped-down
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event dispatched on swipe left.
|
|
|
|
|
* @public
|
|
|
|
|
* @event Swipe#swiped-left
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Event dispatched on swipe right.
|
|
|
|
|
* @public
|
|
|
|
|
* @event Swipe#swiped-right
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Emits events when a swipe action has occurred.
|
|
|
|
|
* @protected
|
|
|
|
|
*/
|
|
|
|
|
_sendEvents() {
|
|
|
|
|
const extentX = this.endX - this.startX;
|
|
|
|
|
const extentY = this.endY - this.startY;
|
|
|
|
|
let dir = null;
|
|
|
|
|
|
|
|
|
|
//Horizontal
|
|
|
|
|
if (Math.abs(extentX) > Math.abs(extentY)) {
|
|
|
|
|
dir = extentX > 0 ? "right" : "left";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Vertical
|
|
|
|
|
if (Math.abs(extentX) < Math.abs(extentY)) {
|
|
|
|
|
dir = extentY > 0 ? "down" : "up";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dir) {
|
|
|
|
|
this._el.dispatchEvent( new CustomEvent('swiped', { detail: { direction: dir } }) );
|
|
|
|
|
this._el.dispatchEvent( new CustomEvent(`swiped-${dir}`) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The configuration object for the Scroller.
|
|
|
|
|
* @typedef {object} scrollerConfig
|
|
|
|
@ -157,6 +247,10 @@ class Scroller {
|
|
|
|
|
this._sizes();
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', this);
|
|
|
|
|
|
|
|
|
|
new Swipe(this._wrapper);
|
|
|
|
|
this._wrapper.addEventListener('swiped-right', () => this.left() );
|
|
|
|
|
this._wrapper.addEventListener('swiped-left', e => this.right() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -332,6 +426,8 @@ class Scroller {
|
|
|
|
|
* @property {string} [texts.download = ⮋]
|
|
|
|
|
* @property {string} [texts.prev = ⮈]
|
|
|
|
|
* @property {string} [texts.next = ⮊]
|
|
|
|
|
* @property {string} [texts.reveal = ᴑ]
|
|
|
|
|
* @property {string} [texts.revealActive = ᴓ]
|
|
|
|
|
* @property {string} [texts.link = ⛓]
|
|
|
|
|
* @property {string} [texts.zoom = 🞕]
|
|
|
|
|
* @property {string} [texts.zoomActive = 🞔]
|
|
|
|
@ -342,6 +438,8 @@ class Scroller {
|
|
|
|
|
* @property {string} [titles.download = Download this image]
|
|
|
|
|
* @property {string} [titles.prev = Previous image]
|
|
|
|
|
* @property {string} [titles.next = Next image]
|
|
|
|
|
* @property {string} [titles.reveal = Reveal image]
|
|
|
|
|
* @property {string} [titles.revealActive = Re-hide image]
|
|
|
|
|
* @property {string} [titles.link = More information]
|
|
|
|
|
* @property {string} [titles.zoom = Enlarge image (drag to move the image around)]
|
|
|
|
|
* @property {string} [titles.zoomActive = Reset image to fit screen]
|
|
|
|
@ -350,13 +448,15 @@ class Scroller {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The available buttons which can be set in the Image Viewer configuration.
|
|
|
|
|
* Note that zoom has an additional 'active' and 'disabled' state setting.
|
|
|
|
|
* Note that reveal and zoom have additional 'active' states, and zoom also has a 'disabled' state.
|
|
|
|
|
* @typedef {object} imageViewerButtons
|
|
|
|
|
* @property {string} [cue] - The cue shown when hovering over an image to indicate the viewer is available.
|
|
|
|
|
* @property {string} [hide] - The button to hide/close the viewer
|
|
|
|
|
* @property {string} [download] - The button to download the current image in the viewer
|
|
|
|
|
* @property {string} [prev] - The button to show the previous image
|
|
|
|
|
* @property {string} [next] - The button to show the next image
|
|
|
|
|
* @property {string} [reveal] - The button to reveal a blurred image
|
|
|
|
|
* @property {string} [revealActive] - The button to hide turn off reveal - i.e. re-blur the image.
|
|
|
|
|
* @property {string} [link] - The button for the image's link
|
|
|
|
|
* @property {string} [zoom] - The button to zoom the image to full size and activate panning
|
|
|
|
|
* @property {string} [zoomActive] - Properties for the zoom button when it's active
|
|
|
|
@ -379,6 +479,8 @@ const defaultImageViewerConfig = {
|
|
|
|
|
download: "⮋",
|
|
|
|
|
prev: "⮈",
|
|
|
|
|
next: "⮊",
|
|
|
|
|
reveal: "ᴑ",
|
|
|
|
|
revealActive: "ᴓ",
|
|
|
|
|
link: "⛓",
|
|
|
|
|
zoom: "🞕",
|
|
|
|
|
zoomActive: "🞔",
|
|
|
|
@ -389,6 +491,8 @@ const defaultImageViewerConfig = {
|
|
|
|
|
download: "",
|
|
|
|
|
prev: "",
|
|
|
|
|
next: "",
|
|
|
|
|
reveal: "",
|
|
|
|
|
revealActive: "",
|
|
|
|
|
link: "",
|
|
|
|
|
zoom: "",
|
|
|
|
|
zoomActive: "",
|
|
|
|
@ -399,6 +503,8 @@ const defaultImageViewerConfig = {
|
|
|
|
|
download: "Download this image",
|
|
|
|
|
prev: "Previous image",
|
|
|
|
|
next: "Next image",
|
|
|
|
|
reveal: "Reveal image",
|
|
|
|
|
revealActive: "Blur image",
|
|
|
|
|
link: "More information",
|
|
|
|
|
zoom: "Enlarge image (drag to move the image around)",
|
|
|
|
|
zoomActive: "Reset image to fit screen",
|
|
|
|
@ -514,6 +620,7 @@ class ImageViewer {
|
|
|
|
|
el.alt = img.getAttribute("alt");
|
|
|
|
|
this._loader.style.visibility = "hidden";
|
|
|
|
|
this._updateCaption(n);
|
|
|
|
|
this.revealToggle(!img.dataset.hasOwnProperty("reveal") || !img.dataset.reveal == 'true');
|
|
|
|
|
this._updateControls();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -601,6 +708,17 @@ class ImageViewer {
|
|
|
|
|
e.currentTarget.classList.toggle("zoomed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle click events for toggling the reveal status
|
|
|
|
|
* @protected
|
|
|
|
|
* @param {HTMLElement} e The click event
|
|
|
|
|
*/
|
|
|
|
|
reveal(e) {
|
|
|
|
|
const state = this._imgDisplay.style.filter.includes("blur");
|
|
|
|
|
this.revealToggle(state);
|
|
|
|
|
this.btnToggle(e.currentTarget, state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggle the specified button on or off as specified
|
|
|
|
|
* @protected
|
|
|
|
@ -644,6 +762,23 @@ class ImageViewer {
|
|
|
|
|
pz.setStyle("cursor", "auto");
|
|
|
|
|
pz.destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the reveal state.
|
|
|
|
|
* @public
|
|
|
|
|
* @param {boolean} [reveal = true]
|
|
|
|
|
*/
|
|
|
|
|
revealToggle(reveal = true) {
|
|
|
|
|
const caption = this._overlay.querySelector(".caption");
|
|
|
|
|
|
|
|
|
|
if (reveal) {
|
|
|
|
|
this._imgDisplay.style.filter = "";
|
|
|
|
|
caption.style.color = "";
|
|
|
|
|
} else {
|
|
|
|
|
this._imgDisplay.style.filter = "blur(1em)";
|
|
|
|
|
caption.style.color = getComputedStyle(caption).getPropertyValue('background-color');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create the overlay and insert in the document
|
|
|
|
@ -674,8 +809,14 @@ class ImageViewer {
|
|
|
|
|
overlay.append(this._createControls());
|
|
|
|
|
overlay.addEventListener("keydown", (e) => this._shortcutsEventListener(e));
|
|
|
|
|
|
|
|
|
|
new Swipe(overlay);
|
|
|
|
|
overlay.addEventListener('swiped-right', () => this.prev() );
|
|
|
|
|
overlay.addEventListener('swiped-left', e => this.next() );
|
|
|
|
|
overlay.addEventListener('swiped-up', e => this.hide() );
|
|
|
|
|
|
|
|
|
|
this._overlay = overlay;
|
|
|
|
|
this._imgDisplay = activeImg;
|
|
|
|
|
this._caption = caption;
|
|
|
|
|
this._loader = loader;
|
|
|
|
|
this._active = false;
|
|
|
|
|
|
|
|
|
@ -738,6 +879,8 @@ class ImageViewer {
|
|
|
|
|
if (this._images.length > 1) {
|
|
|
|
|
btns.push("next", "prev");
|
|
|
|
|
}
|
|
|
|
|
btns.push("reveal");
|
|
|
|
|
|
|
|
|
|
const anchors = [ "download", "link" ];
|
|
|
|
|
|
|
|
|
|
for (const b of btns) {
|
|
|
|
@ -780,6 +923,14 @@ class ImageViewer {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const btnReveal = document.getElementById("btn-reveal");
|
|
|
|
|
if (this._images[i].dataset.reveal == 'true') {
|
|
|
|
|
btnReveal.style.display = "block";
|
|
|
|
|
} else {
|
|
|
|
|
btnReveal.style.display = "none";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this._config.panzoom) {
|
|
|
|
|
console.log(`Shown: ${img.width}; Actual: ${img.naturalWidth}`);
|
|
|
|
|
const btnZoom = document.getElementById("btn-zoom");
|
|
|
|
@ -823,4 +974,92 @@ class ImageViewer {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { ImageViewer, Scroller };
|
|
|
|
|
/**
|
|
|
|
|
* A simple animated visibility toggler.
|
|
|
|
|
* @public
|
|
|
|
|
* @param {HTMLElement} source The element which triggers the toggle, e.g. a `<button>`
|
|
|
|
|
* @param {HTMLElement|null} [target = null] The element of which to toggle the visibility.
|
|
|
|
|
* If omitted, the `data-toggle-target` attribute of the source will be checked for an ID.
|
|
|
|
|
* If neither is provided, an error is thrown.
|
|
|
|
|
* @param {string} [toggleText = ''] The replacement text for the source when its state is toggled.
|
|
|
|
|
* If omitted, the `data-toggle-text` attribute of the source will be used.
|
|
|
|
|
* If neither is provided, the source text remains static.
|
|
|
|
|
*/
|
|
|
|
|
class Toggler {
|
|
|
|
|
|
|
|
|
|
constructor(source, target = null, toggleText = null) {
|
|
|
|
|
this._source = source;
|
|
|
|
|
try {
|
|
|
|
|
this._target = ( target instanceof HTMLElement ? target : document.getElementById(source.dataset.toggleTarget) );
|
|
|
|
|
} catch {
|
|
|
|
|
throw new Error(`Invalid target set. The target element must be provided either directly or with a data attribute`);
|
|
|
|
|
}
|
|
|
|
|
this._target.classList.add('toggle-view');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this._toggleText = ( toggleText ? toggleText : source.dataset.toggleText );
|
|
|
|
|
} catch {
|
|
|
|
|
console.log("No toggle text");
|
|
|
|
|
}
|
|
|
|
|
this._sourceTextTarget = ( this._source.querySelector(".toggle-text") ? this._source.querySelector(".toggle-text") : this._source );
|
|
|
|
|
this._origText = this._sourceTextTarget.textContent;
|
|
|
|
|
|
|
|
|
|
this._source.addEventListener("click", () => this.toggle() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggle the state of the target.
|
|
|
|
|
* @public
|
|
|
|
|
*/
|
|
|
|
|
toggle() {
|
|
|
|
|
if (this._target.classList.contains('visible')) {
|
|
|
|
|
this.hide();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show the target.
|
|
|
|
|
* @public
|
|
|
|
|
*/
|
|
|
|
|
show() {
|
|
|
|
|
const target = this._target;
|
|
|
|
|
const height = this._checkHeight(target);
|
|
|
|
|
|
|
|
|
|
if (this._toggleText) this._sourceTextTarget.textContent = this._toggleText;
|
|
|
|
|
|
|
|
|
|
target.classList.add("visible");
|
|
|
|
|
target.style.height = height;
|
|
|
|
|
|
|
|
|
|
setTimeout( () => target.style.height = '', 250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hide the target.
|
|
|
|
|
* @public
|
|
|
|
|
*/
|
|
|
|
|
hide() {
|
|
|
|
|
const target = this._target;
|
|
|
|
|
|
|
|
|
|
if (this._toggleText) this._sourceTextTarget.textContent = this._origText;
|
|
|
|
|
|
|
|
|
|
target.style.height = target.scrollHeight + "px";
|
|
|
|
|
setTimeout( () => target.style.height = 0, 1);
|
|
|
|
|
setTimeout( () => target.classList.remove("visible"), 250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check the auto height of an element.
|
|
|
|
|
* @protected
|
|
|
|
|
* @param {HTMLElement} el
|
|
|
|
|
*/
|
|
|
|
|
_checkHeight(el) {
|
|
|
|
|
el.style.display = 'block';
|
|
|
|
|
const h = el.scrollHeight + "px";
|
|
|
|
|
el.style.display = '';
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { ImageViewer, Scroller, Toggler };
|
|
|
|
|