summaryrefslogtreecommitdiffstats
path: root/public/js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js')
-rw-r--r--public/js/album.js16
-rw-r--r--public/js/slideshow.js115
2 files changed, 118 insertions, 13 deletions
diff --git a/public/js/album.js b/public/js/album.js
index fe492af..dd54ed2 100644
--- a/public/js/album.js
+++ b/public/js/album.js
@@ -128,6 +128,22 @@ window.addEventListener('DOMContentLoaded', () => {
});
})();
+// Slideshow launch options (Shuffle / Full screen checkboxes next to the button)
+(function () {
+ const link = document.getElementById('ss-launch');
+ if (!link) return;
+ const base = link.dataset.base;
+ function update() {
+ const p = [];
+ if (document.getElementById('ss-opt-shuffle').checked) p.push('shuffle=1');
+ if (document.getElementById('ss-opt-fullscreen').checked) p.push('fullscreen=1');
+ link.href = base + (p.length ? '?' + p.join('&') : '');
+ }
+ ['ss-opt-shuffle', 'ss-opt-fullscreen'].forEach(id =>
+ document.getElementById(id).addEventListener('change', update)
+ );
+})();
+
// Touch swipe
(function () {
let startX = null;
diff --git a/public/js/slideshow.js b/public/js/slideshow.js
index 2260034..a732962 100644
--- a/public/js/slideshow.js
+++ b/public/js/slideshow.js
@@ -3,6 +3,7 @@
let ssIdx = 0;
let ssTimer = null;
let ssPlaying = true;
+let ssQueue = SS_ENTRIES.slice();
const img = document.getElementById('ss-img');
const vid = document.getElementById('ss-video');
@@ -16,25 +17,22 @@ function ssInterval() {
}
function ssShow(i, instant) {
- const e = SS_ENTRIES[i];
+ const e = ssQueue[i];
if (!e) return;
ssIdx = i;
title.textContent = e.title !== e.name ? e.title : '';
cap.textContent = e.caption || '';
- ctr.textContent = `${i + 1} / ${SS_ENTRIES.length}`;
+ ctr.textContent = `${i + 1} / ${ssQueue.length}`;
if (instant) {
- // First load — show without transition
applyEntry(e);
return;
}
if (e.type === 'video') {
- // No preload possible for video; fade out then swap
crossFade(() => applyEntry(e));
} else {
- // Preload the image so the fade-in shows it immediately
const preload = new Image();
preload.onload = preload.onerror = () => crossFade(() => applyEntry(e));
preload.src = e.src;
@@ -46,7 +44,7 @@ function applyEntry(e) {
img.style.display = 'none';
vid.style.display = '';
vid.src = e.src;
- void vid.offsetWidth; // flush so transition fires
+ void vid.offsetWidth;
vid.classList.remove('fading');
vid.play().catch(() => {});
} else {
@@ -55,7 +53,7 @@ function applyEntry(e) {
img.style.display = '';
img.src = e.src;
img.alt = e.title;
- void img.offsetWidth; // flush so transition fires
+ void img.offsetWidth;
img.classList.remove('fading');
}
}
@@ -63,26 +61,26 @@ function applyEntry(e) {
function crossFade(cb) {
img.classList.add('fading');
vid.classList.add('fading');
- setTimeout(cb, 500); // matches CSS transition duration
+ setTimeout(cb, 500);
}
function ssSchedule() {
clearTimeout(ssTimer);
- if (ssPlaying && SS_ENTRIES.length > 1) {
+ if (ssPlaying && ssQueue.length > 1) {
ssTimer = setTimeout(() => {
- ssShow((ssIdx + 1) % SS_ENTRIES.length);
+ ssShow((ssIdx + 1) % ssQueue.length);
ssSchedule();
}, ssInterval());
}
}
function ssNext() {
- ssShow((ssIdx + 1) % SS_ENTRIES.length);
+ ssShow((ssIdx + 1) % ssQueue.length);
if (ssPlaying) ssSchedule();
}
function ssPrev() {
- ssShow((ssIdx - 1 + SS_ENTRIES.length) % SS_ENTRIES.length);
+ ssShow((ssIdx - 1 + ssQueue.length) % ssQueue.length);
if (ssPlaying) ssSchedule();
}
@@ -92,9 +90,75 @@ function ssToggle() {
if (ssPlaying) ssSchedule(); else clearTimeout(ssTimer);
}
+// ── Shuffle ────────────────────────────────────────────────────────────────────
+
+function shuffle(arr) {
+ const a = arr.slice();
+ for (let i = a.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [a[i], a[j]] = [a[j], a[i]];
+ }
+ return a;
+}
+
+// ── Full screen ────────────────────────────────────────────────────────────────
+
+function ssFsActive() {
+ return !!(document.fullscreenElement || document.webkitFullscreenElement);
+}
+
+function ssFsEnter() {
+ const el = document.documentElement;
+ return (el.requestFullscreen || el.webkitRequestFullscreen).call(el);
+}
+
+function ssFsExit() {
+ return (document.exitFullscreen || document.webkitExitFullscreen).call(document);
+}
+
+function ssFsToggle() {
+ if (ssFsActive()) ssFsExit();
+ else ssFsEnter().catch(() => {});
+}
+
+function onFsChange() {
+ const inFs = ssFsActive();
+ document.body.classList.toggle('ss-fullscreen', inFs);
+ if (inFs) {
+ const hint = document.createElement('div');
+ hint.textContent = 'Press Esc to exit full screen';
+ hint.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.75);color:#fff;padding:8px 18px;border-radius:6px;font-size:.9rem;pointer-events:none;transition:opacity 1.2s';
+ document.body.appendChild(hint);
+ setTimeout(() => { hint.style.opacity = '0'; }, 1800);
+ setTimeout(() => { hint.remove(); }, 3100);
+ } else {
+ if (ssPlaying) ssToggle(); // pause so current photo stays visible
+ }
+}
+
+document.addEventListener('fullscreenchange', onFsChange);
+document.addEventListener('webkitfullscreenchange', onFsChange);
+
+// Click/tap anywhere in the stage → open photo in its album lightbox.
+// Skip when the video element is the target so its native controls still work.
+// Read the live src attribute rather than ssIdx, which updates ahead of the
+// cross-fade and would point to the next photo while the old one is still visible.
+document.getElementById('ss-stage').addEventListener('click', ev => {
+ if (ev.target === vid || vid.contains(ev.target)) return;
+ const activeSrc = vid.style.display !== 'none' ? vid.getAttribute('src') : img.getAttribute('src');
+ const e = ssQueue.find(entry => entry.src === activeSrc) || ssQueue[ssIdx];
+ if (!e) return;
+ const lastSlash = e.file_rel.lastIndexOf('/');
+ const albumRel = lastSlash >= 0 ? e.file_rel.slice(0, lastSlash) : '';
+ window.location.href = '/browse/' + (albumRel ? albumRel + '/' : '') + '#photo=' + encodeURIComponent(e.name);
+});
+
+// ── Keyboard & touch ───────────────────────────────────────────────────────────
+
document.addEventListener('keydown', e => {
if (e.key === 'ArrowRight') ssNext();
if (e.key === 'ArrowLeft') ssPrev();
+ if (e.key === 'f') ssFsToggle();
if (e.key === ' ') { e.preventDefault(); ssToggle(); }
});
@@ -107,7 +171,32 @@ document.addEventListener('touchend', e => {
swipeX = null;
}, { passive: true });
-if (SS_ENTRIES.length > 0) {
+// ── Init ───────────────────────────────────────────────────────────────────────
+
+const ssParams = new URLSearchParams(location.search);
+if (ssParams.get('shuffle') === '1') {
+ ssQueue = shuffle(SS_ENTRIES);
+}
+
+if (ssQueue.length > 0) {
ssShow(0, true);
ssSchedule();
}
+
+// Browsers require a live user gesture for requestFullscreen — entering it
+// automatically on page load is always blocked. We wait for the first tap.
+if (ssParams.get('fullscreen') === '1') {
+ const overlay = document.createElement('div');
+ overlay.textContent = 'Click or tap anywhere to enter full screen.';
+ overlay.style.cssText = [
+ 'position:fixed;inset:0;z-index:200',
+ 'display:flex;align-items:center;justify-content:center',
+ 'color:rgba(255,255,255,.75);font-size:1.1rem;cursor:pointer',
+ 'background:rgba(0,0,0,.45)',
+ ].join(';');
+ document.body.appendChild(overlay);
+ overlay.addEventListener('click', () => {
+ overlay.remove();
+ ssFsEnter().catch(() => {});
+ }, { once: true });
+}