1
0
Fork 0

Import existing project

This commit is contained in:
Caileb 2025-05-26 12:42:36 -05:00
parent 7887817595
commit 80b0cc4939
125 changed files with 16980 additions and 0 deletions

427
develop/js/lightbox.js Normal file
View 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();
}
})();