/* ---------- Gallery Management ---------- */
/* DOM Elements */
let crumbsEl = document.getElementById('crumbs');
let foldersTrack = document.getElementById('foldersTrack');
let emptyFolders = document.getElementById('emptyFolders');
let galleryTrack = document.getElementById('galleryTrack');
let emptyImages = document.getElementById('emptyImages');
let goUpBtn = document.getElementById('goUpBtn');
let refreshBtn = document.getElementById('refreshBtn');
let imagesPanel = document.getElementById('imagesPanel');
/* State */
let currentSub = '';
let hideTimer = null;
let isGalleryVisible = false;
/* ---------- Auto-Hide Functionality ---------- */
function resetHideTimer() {
if (hideTimer) {
clearTimeout(hideTimer);
}
if (isGalleryVisible) {
hideTimer = setTimeout(() => {
hideGallery();
}, 5000); // 5 seconds
}
}
function showGallery() {
if (imagesPanel) {
imagesPanel.classList.add('active');
isGalleryVisible = true;
resetHideTimer();
}
}
function hideGallery() {
if (imagesPanel) {
imagesPanel.classList.remove('active');
isGalleryVisible = false;
if (hideTimer) {
clearTimeout(hideTimer);
hideTimer = null;
}
}
}
function addActivityListeners(element) {
if (!element) return;
const events = ['click', 'mousemove', 'mouseenter', 'scroll', 'touchstart', 'touchmove'];
events.forEach(eventType => {
element.addEventListener(eventType, resetHideTimer, { passive: true });
});
}
/* ---------- API Communication ---------- */
async function fetchDir(sub = '') {
const res = await fetch(`media.php?sub=${encodeURIComponent(sub)}`, {cache:'no-store'});
if(!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
/* ---------- Rendering Functions ---------- */
function renderBreadcrumb(bc) {
crumbsEl.innerHTML = '';
bc.forEach((c, i) => {
const b = document.createElement('button');
b.textContent = c.label;
b.onclick = () => {
openSub(c.sub);
resetHideTimer();
};
crumbsEl.appendChild(b);
if(i < bc.length - 1) {
const s = document.createElement('span');
s.className = 'sep';
s.textContent = '›';
crumbsEl.appendChild(s);
}
});
}
function renderFolders(items) {
foldersTrack.innerHTML = '';
emptyFolders.hidden = !!items.length;
for(const f of items) {
const card = document.createElement('div');
card.className = 'folder-card';
card.innerHTML = `<span>📂</span><span>${f.name}</span>`;
card.onclick = () => {
openSub(f.sub);
resetHideTimer();
};
foldersTrack.appendChild(card);
}
}
function renderImages(items) {
galleryTrack.innerHTML = '';
emptyImages.hidden = !!items.length;
for(const im of items) {
const img = document.createElement('img');
img.alt = im.name;
img.loading = 'lazy';
img.decoding = 'async';
img.src = im.url;
img.onclick = () => {
window.CoreAPI.setImage(im.url);
resetHideTimer();
};
galleryTrack.appendChild(img);
}
}
/* ---------- Auto Tile Size Detection ---------- */
function inferTileFromPath(sub) {
if (!sub) return null;
const parts = sub.split('/').filter(Boolean).reverse();
for (const p of parts) {
const n = parseInt(p, 10);
if (String(n) === p && n > 0) return n;
}
return null;
}
/* ---------- Navigation ---------- */
async function openSub(sub) {
try {
const data = await fetchDir(sub);
currentSub = data.cwd || '';
renderBreadcrumb(data.breadcrumb || []);
renderFolders(data.folders || []);
renderImages(data.images || []);
// Auto-detect tile size from folder name
const inferred = inferTileFromPath(currentSub);
if (inferred) {
window.CoreAPI.setTileSize(inferred);
}
// Reset timer when navigating
resetHideTimer();
} catch(err) {
console.error(err);
alert('Failed to load folder.');
}
}
function parentOf(sub) {
if(!sub) return '';
const p = sub.split('/').filter(Boolean);
p.pop();
return p.join('/');
}
/* ---------- Event Handlers ---------- */
function initGalleryEvents() {
goUpBtn.addEventListener('click', () => {
openSub(parentOf(currentSub));
resetHideTimer();
});
refreshBtn.addEventListener('click', () => {
openSub(currentSub);
resetHideTimer();
});
// Add activity listeners to all gallery elements
addActivityListeners(imagesPanel);
addActivityListeners(crumbsEl);
addActivityListeners(foldersTrack);
addActivityListeners(galleryTrack);
// Listen for images button click to show gallery
const imagesBtn = document.getElementById('imagesBtn');
if (imagesBtn) {
imagesBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (isGalleryVisible) {
hideGallery();
} else {
showGallery();
}
});
}
// Hide gallery when clicking outside
document.addEventListener('click', (e) => {
if (isGalleryVisible && imagesPanel && !imagesPanel.contains(e.target) &&
e.target !== document.getElementById('imagesBtn')) {
hideGallery();
}
});
}
/* ---------- Initialization ---------- */
function initializeGallery() {
try {
// Get DOM elements (reassign in case they weren't available initially)
crumbsEl = document.getElementById('crumbs');
foldersTrack = document.getElementById('foldersTrack');
emptyFolders = document.getElementById('emptyFolders');
galleryTrack = document.getElementById('galleryTrack');
emptyImages = document.getElementById('emptyImages');
goUpBtn = document.getElementById('goUpBtn');
refreshBtn = document.getElementById('refreshBtn');
imagesPanel = document.getElementById('imagesPanel');
// Check required DOM elements and dependencies
if (!crumbsEl || !foldersTrack || !galleryTrack || !goUpBtn) {
throw new Error('Required DOM elements not found');
}
if (!window.CoreAPI || typeof window.CoreAPI.setImage !== 'function') {
throw new Error('CoreAPI not available');
}
initGalleryEvents();
openSub(''); // Load root folder
} catch (error) {
alert(`Gallery module failed to initialize: ${error.message}\n\nNote: Gallery requires media.php to be present and working.`);
// Don't throw here since gallery failure shouldn't break the whole app
}
}
// Export API for other modules
window.GalleryAPI = {
openSub,
currentSub: () => currentSub,
showGallery,
hideGallery,
resetHideTimer
};