Import existing project
This commit is contained in:
parent
7887817595
commit
80b0cc4939
125 changed files with 16980 additions and 0 deletions
427
develop/js/lightbox.js
Normal file
427
develop/js/lightbox.js
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/**
|
||||
* EasyLightbox - A simple, lightweight lightbox for images
|
||||
*/
|
||||
(function() {
|
||||
// Default options
|
||||
const defaultOptions = {
|
||||
selector: '.lightbox-img, #flowDiagram', // Images that should trigger lightbox
|
||||
captionAttribute: 'data-caption', // Attribute to retrieve caption from
|
||||
zoomable: true, // Whether to enable zoom controls
|
||||
maxZoom: 300, // Maximum zoom percentage
|
||||
minZoom: 100, // Minimum zoom percentage
|
||||
closeOnEsc: true, // Close on escape key
|
||||
closeOnOutsideClick: true // Close when clicking outside image
|
||||
};
|
||||
|
||||
// Create global object
|
||||
window.EasyLightbox = {
|
||||
options: { ...defaultOptions },
|
||||
|
||||
// Initialize with custom options
|
||||
init: function(customOptions = {}) {
|
||||
// Merge default options with custom options
|
||||
this.options = { ...defaultOptions, ...customOptions };
|
||||
|
||||
// Create lightbox container if it doesn't exist
|
||||
this._createLightbox();
|
||||
|
||||
// Initialize listeners for all matching elements
|
||||
this._initImageListeners();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Create the lightbox HTML structure if it doesn't exist
|
||||
_createLightbox: function() {
|
||||
// Check if lightbox already exists
|
||||
if (document.getElementById("imageLightbox")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create lightbox container
|
||||
const lightbox = document.createElement("div");
|
||||
lightbox.id = "imageLightbox";
|
||||
lightbox.className = "lightbox";
|
||||
|
||||
// Create lightbox content with simplified HTML
|
||||
lightbox.innerHTML = `
|
||||
<div class="lightbox-content">
|
||||
<div class="lightbox-close" id="lightboxClose">X</div>
|
||||
<div class="lightbox-img-container">
|
||||
<img class="lightbox-img" id="lightboxImg" src="" alt="Enlarged image" draggable="false">
|
||||
</div>
|
||||
<div class="lightbox-caption" id="lightboxCaption"></div>
|
||||
${
|
||||
this.options.zoomable
|
||||
? `
|
||||
<div class="zoom-controls">
|
||||
<span class="zoom-label">Zoom:</span>
|
||||
<input type="range" min="${this.options.minZoom}" max="${this.options.maxZoom}" value="100" class="zoom-slider" id="zoomSlider">
|
||||
<span class="zoom-value" id="zoomValue">100%</span>
|
||||
</div>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add lightbox CSS link if not already present
|
||||
if (!document.getElementById("lightbox-styles")) {
|
||||
const link = document.createElement("link");
|
||||
link.id = "lightbox-styles";
|
||||
link.rel = "stylesheet";
|
||||
link.href = "/css/lightbox.css";
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
// Add to document
|
||||
document.body.appendChild(lightbox);
|
||||
|
||||
// Cache DOM elements
|
||||
this.elements = {
|
||||
lightbox: lightbox,
|
||||
lightboxImg: document.getElementById("lightboxImg"),
|
||||
lightboxCaption: document.getElementById("lightboxCaption"),
|
||||
lightboxClose: document.getElementById("lightboxClose"),
|
||||
zoomSlider: document.getElementById("zoomSlider"),
|
||||
zoomValue: document.getElementById("zoomValue")
|
||||
};
|
||||
|
||||
// Initialize event handlers inside the lightbox
|
||||
this._initLightboxHandlers();
|
||||
},
|
||||
|
||||
// Initialize listeners for images that should open the lightbox
|
||||
_initImageListeners: function() {
|
||||
const images = document.querySelectorAll(this.options.selector);
|
||||
const self = this;
|
||||
|
||||
images.forEach(img => {
|
||||
// *** FIX: Skip the actual lightbox image itself ***
|
||||
if (img.id === "lightboxImg") return;
|
||||
|
||||
// Skip if already initialized
|
||||
if (img.dataset.lightboxInitialized) return;
|
||||
|
||||
img.dataset.lightboxInitialized = "true";
|
||||
img.style.cursor = "pointer";
|
||||
|
||||
img.addEventListener("click", function() {
|
||||
let caption = this.getAttribute(self.options.captionAttribute);
|
||||
if (this.id === "flowDiagram" || !caption) {
|
||||
caption = "Basic POW Flow Diagram";
|
||||
}
|
||||
self.open(this, caption);
|
||||
});
|
||||
});
|
||||
|
||||
// Special handling for flowDiagram if not caught by selector
|
||||
const flowDiagram = document.getElementById("flowDiagram");
|
||||
if (flowDiagram && !flowDiagram.dataset.lightboxInitialized) {
|
||||
flowDiagram.dataset.lightboxInitialized = "true";
|
||||
flowDiagram.style.cursor = "pointer";
|
||||
flowDiagram.addEventListener("click", function() {
|
||||
self.open(this, "Basic POW Flow Diagram");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Initialize lightbox event handlers for zooming, closing, etc.
|
||||
_initLightboxHandlers: function() {
|
||||
const self = this;
|
||||
const elements = this.elements;
|
||||
|
||||
let isDragging = false;
|
||||
let startX, startY, startPanX, startPanY;
|
||||
let panX = 0,
|
||||
panY = 0;
|
||||
|
||||
// Add zoom slider handler
|
||||
if (this.options.zoomable && elements.zoomSlider) {
|
||||
elements.zoomSlider.addEventListener("input", function() {
|
||||
const value = this.value;
|
||||
elements.zoomValue.textContent = value + "%";
|
||||
updateTransform();
|
||||
});
|
||||
}
|
||||
|
||||
// Add close button handler
|
||||
if (elements.lightboxClose) {
|
||||
elements.lightboxClose.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
self.close();
|
||||
});
|
||||
}
|
||||
|
||||
// Add outside click handler
|
||||
if (this.options.closeOnOutsideClick) {
|
||||
elements.lightbox.addEventListener("click", function(e) {
|
||||
if (e.target === elements.lightbox) {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add escape key handler
|
||||
if (this.options.closeOnEsc) {
|
||||
document.addEventListener("keydown", function(e) {
|
||||
if (e.key === "Escape" && elements.lightbox.classList.contains("active")) {
|
||||
self.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add drag handlers for panning when zoomed
|
||||
if (elements.lightboxImg) {
|
||||
elements.lightboxImg.addEventListener("mousedown", startDrag);
|
||||
elements.lightboxImg.addEventListener("touchstart", startDrag);
|
||||
}
|
||||
|
||||
function startDrag(e) {
|
||||
// Only allow dragging when zoomed in
|
||||
if (!self.options.zoomable || parseInt(elements.zoomSlider.value) <= 100)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (e.type === "touchstart") {
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
} else {
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
}
|
||||
|
||||
startPanX = panX;
|
||||
startPanY = panY;
|
||||
isDragging = true;
|
||||
|
||||
elements.lightboxImg.classList.add("grabbing");
|
||||
|
||||
document.addEventListener("mousemove", doDrag);
|
||||
document.addEventListener("touchmove", doDrag);
|
||||
document.addEventListener("mouseup", stopDrag);
|
||||
document.addEventListener("touchend", stopDrag);
|
||||
document.addEventListener("mouseleave", stopDrag);
|
||||
}
|
||||
|
||||
function doDrag(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
// Prevent default scroll/zoom behavior on touch devices
|
||||
e.preventDefault();
|
||||
|
||||
let clientX, clientY;
|
||||
if (e.type === "touchmove") {
|
||||
// Ensure there's a touch point
|
||||
if (e.touches.length === 0) return;
|
||||
clientX = e.touches[0].clientX;
|
||||
clientY = e.touches[0].clientY;
|
||||
} else {
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
}
|
||||
|
||||
const deltaX = clientX - startX;
|
||||
const deltaY = clientY - startY;
|
||||
|
||||
panX = startPanX + deltaX;
|
||||
panY = startPanY + deltaY;
|
||||
|
||||
// Apply the transform immediately for live dragging
|
||||
updateTransform();
|
||||
}
|
||||
|
||||
function stopDrag() {
|
||||
if (!isDragging) return;
|
||||
|
||||
isDragging = false;
|
||||
elements.lightboxImg.classList.remove("grabbing");
|
||||
|
||||
document.removeEventListener("mousemove", doDrag);
|
||||
document.removeEventListener("touchmove", doDrag);
|
||||
document.removeEventListener("mouseup", stopDrag);
|
||||
document.removeEventListener("touchend", stopDrag);
|
||||
document.removeEventListener("mouseleave", stopDrag);
|
||||
}
|
||||
|
||||
function updateTransform() {
|
||||
if (!self.options.zoomable) return;
|
||||
const scale = parseInt(elements.zoomSlider.value) / 100;
|
||||
elements.lightboxImg.style.transform = `scale(${scale}) translate(${
|
||||
panX / scale
|
||||
}px, ${panY / scale}px)`;
|
||||
}
|
||||
|
||||
// Prevent scrolling on mobile when interacting with the lightbox
|
||||
const isMobile = window.matchMedia(
|
||||
"(max-width: 768px), (max-width: 1024px) and (orientation: landscape)"
|
||||
).matches;
|
||||
if (isMobile && elements.lightboxImg) {
|
||||
elements.lightboxImg.addEventListener("touchmove", function(e) {
|
||||
if (e.touches.length > 1) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Open the lightbox with a specific image
|
||||
open: async function(imageElement, caption) {
|
||||
if (!imageElement || !this.elements) return;
|
||||
|
||||
const elements = this.elements;
|
||||
let panX = 0, panY = 0;
|
||||
|
||||
// Remove any previous SVG
|
||||
if (elements.lightboxImg && elements.lightboxImg.parentNode) {
|
||||
elements.lightboxImg.style.display = '';
|
||||
const prevSvg = elements.lightboxImg.parentNode.querySelector('svg.injected-svg');
|
||||
if (prevSvg) prevSvg.remove();
|
||||
}
|
||||
|
||||
const src = imageElement.src || imageElement.getAttribute("data-fullsize") || "";
|
||||
const isSVG = src.toLowerCase().endsWith('.svg');
|
||||
|
||||
// Helper for zoom slider value
|
||||
function getZoom() {
|
||||
return elements.zoomSlider ? parseInt(elements.zoomSlider.value) / 100 : 1;
|
||||
}
|
||||
|
||||
// Helper to update SVG transform
|
||||
function updateSVGTransform(svg, svgPanX, svgPanY, scale) {
|
||||
svg.style.transform = `scale(${scale}) translate(${svgPanX/scale}px, ${svgPanY/scale}px)`;
|
||||
}
|
||||
|
||||
if (isSVG) {
|
||||
elements.lightboxImg.style.display = 'none';
|
||||
try {
|
||||
const resp = await fetch(src);
|
||||
let svgText = await resp.text();
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = svgText;
|
||||
const svg = tempDiv.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.classList.add('injected-svg');
|
||||
svg.style.transformOrigin = 'center center';
|
||||
svg.style.maxWidth = '100%';
|
||||
svg.style.maxHeight = '100%';
|
||||
svg.style.display = 'block';
|
||||
svg.style.cursor = 'grab';
|
||||
svg.style.userSelect = 'none';
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
elements.lightboxImg.parentNode.appendChild(svg);
|
||||
|
||||
// Set default zoom to 1.0x (100%)
|
||||
if (elements.zoomSlider) {
|
||||
elements.zoomSlider.value = 100;
|
||||
elements.zoomValue.textContent = '100%';
|
||||
}
|
||||
let svgPanX = 0, svgPanY = 0;
|
||||
let isDragging = false, startX, startY, startPanX = 0, startPanY = 0;
|
||||
let currentScale = getZoom();
|
||||
updateSVGTransform(svg, svgPanX, svgPanY, currentScale);
|
||||
|
||||
// Drag logic for SVG
|
||||
svg.addEventListener('mousedown', startDrag);
|
||||
svg.addEventListener('touchstart', startDrag);
|
||||
function startDrag(e) {
|
||||
e.preventDefault();
|
||||
isDragging = true;
|
||||
svg.classList.add('grabbing');
|
||||
if (e.type === 'touchstart') {
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
} else {
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
}
|
||||
startPanX = svgPanX;
|
||||
startPanY = svgPanY;
|
||||
document.addEventListener('mousemove', doDrag);
|
||||
document.addEventListener('touchmove', doDrag);
|
||||
document.addEventListener('mouseup', stopDrag);
|
||||
document.addEventListener('touchend', stopDrag);
|
||||
document.addEventListener('mouseleave', stopDrag);
|
||||
}
|
||||
function doDrag(e) {
|
||||
if (!isDragging) return;
|
||||
e.preventDefault();
|
||||
let clientX, clientY;
|
||||
if (e.type === 'touchmove') {
|
||||
if (e.touches.length === 0) return;
|
||||
clientX = e.touches[0].clientX;
|
||||
clientY = e.touches[0].clientY;
|
||||
} else {
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
}
|
||||
const deltaX = clientX - startX;
|
||||
const deltaY = clientY - startY;
|
||||
svgPanX = startPanX + deltaX;
|
||||
svgPanY = startPanY + deltaY;
|
||||
updateSVGTransform(svg, svgPanX, svgPanY, getZoom());
|
||||
}
|
||||
function stopDrag() {
|
||||
if (!isDragging) return;
|
||||
isDragging = false;
|
||||
svg.classList.remove('grabbing');
|
||||
document.removeEventListener('mousemove', doDrag);
|
||||
document.removeEventListener('touchmove', doDrag);
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
document.removeEventListener('touchend', stopDrag);
|
||||
document.removeEventListener('mouseleave', stopDrag);
|
||||
}
|
||||
// Zoom slider controls SVG scale
|
||||
if (elements.zoomSlider) {
|
||||
elements.zoomSlider.oninput = function() {
|
||||
currentScale = getZoom();
|
||||
elements.zoomValue.textContent = Math.round(currentScale * 100) + '%';
|
||||
updateSVGTransform(svg, svgPanX, svgPanY, currentScale);
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
elements.lightboxImg.style.display = '';
|
||||
}
|
||||
} else {
|
||||
elements.lightboxImg.src = src;
|
||||
elements.lightboxImg.style.display = '';
|
||||
elements.lightboxImg.style.transform = "scale(1) translate(0px, 0px)";
|
||||
if (this.options.zoomable && elements.zoomSlider) {
|
||||
elements.zoomSlider.value = 100;
|
||||
elements.zoomValue.textContent = "100%";
|
||||
}
|
||||
}
|
||||
|
||||
const captionText =
|
||||
caption ||
|
||||
imageElement.getAttribute(this.options.captionAttribute) ||
|
||||
imageElement.alt ||
|
||||
imageElement.getAttribute("title") ||
|
||||
"";
|
||||
elements.lightboxCaption.textContent = captionText;
|
||||
|
||||
elements.lightbox.classList.add("active");
|
||||
document.body.style.overflow = "hidden";
|
||||
},
|
||||
|
||||
// Close the lightbox
|
||||
close: function() {
|
||||
if (!this.elements) return;
|
||||
|
||||
this.elements.lightbox.classList.remove("active");
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-initialize on load
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
window.EasyLightbox.init();
|
||||
});
|
||||
} else {
|
||||
window.EasyLightbox.init();
|
||||
}
|
||||
})();
|
||||
Reference in a new issue