136 lines
5.2 KiB
JavaScript
136 lines
5.2 KiB
JavaScript
/* score-reader.js — Page-turn navigation for the full score reader.
|
|
Configuration is read from #score-reader-stage data attributes:
|
|
data-page-count — total number of SVG pages
|
|
data-pages — comma-separated list of absolute page image URLs
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var stage = document.getElementById('score-reader-stage');
|
|
var img = document.getElementById('score-page-img');
|
|
var counter = document.getElementById('score-page-counter');
|
|
var prevBtn = document.getElementById('score-prev');
|
|
var nextBtn = document.getElementById('score-next');
|
|
|
|
if (!stage || !img || !counter || !prevBtn || !nextBtn) return;
|
|
|
|
var rawPages = stage.dataset.pages || '';
|
|
var pages = rawPages.split(',').filter(function (p) { return p.length > 0; });
|
|
var pageCount = pages.length;
|
|
var currentPage = 1;
|
|
|
|
if (pageCount === 0) return; /* nothing to display */
|
|
|
|
/* Read ?p= from the query string for deep linking. */
|
|
var qs = new URLSearchParams(window.location.search);
|
|
var initPage = parseInt(qs.get('p'), 10);
|
|
if (!isNaN(initPage) && initPage >= 1 && initPage <= pageCount) {
|
|
currentPage = initPage;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
Navigation
|
|
------------------------------------------------------------------ */
|
|
|
|
function navigate(page) {
|
|
if (page < 1 || page > pageCount) return;
|
|
currentPage = page;
|
|
|
|
img.src = pages[currentPage - 1];
|
|
img.alt = 'Score page ' + currentPage;
|
|
counter.textContent = 'p. ' + currentPage + ' / ' + pageCount;
|
|
|
|
prevBtn.disabled = (currentPage === 1);
|
|
nextBtn.disabled = (currentPage === pageCount);
|
|
|
|
updateActiveMovement();
|
|
|
|
/* Replace URL so the page is bookmarkable at the current position.
|
|
The back button still returns to the landing page. */
|
|
history.replaceState(null, '', '?p=' + currentPage);
|
|
|
|
/* Preload the adjacent pages for smooth turning. */
|
|
if (currentPage > 1) new Image().src = pages[currentPage - 2];
|
|
if (currentPage < pageCount) new Image().src = pages[currentPage];
|
|
}
|
|
|
|
/* ------------------------------------------------------------------
|
|
Movement buttons — highlight the movement that contains currentPage
|
|
------------------------------------------------------------------ */
|
|
|
|
var mvtButtons = Array.from(document.querySelectorAll('.score-reader-mvt'));
|
|
|
|
function updateActiveMovement() {
|
|
/* Find the last movement whose start page ≤ currentPage. */
|
|
var active = null;
|
|
mvtButtons.forEach(function (btn) {
|
|
var p = parseInt(btn.dataset.page, 10);
|
|
if (!isNaN(p) && p <= currentPage) active = btn;
|
|
});
|
|
mvtButtons.forEach(function (btn) {
|
|
btn.classList.toggle('is-active', btn === active);
|
|
});
|
|
}
|
|
|
|
mvtButtons.forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var p = parseInt(btn.dataset.page, 10);
|
|
if (!isNaN(p)) navigate(p);
|
|
});
|
|
});
|
|
|
|
/* ------------------------------------------------------------------
|
|
Prev / next buttons
|
|
------------------------------------------------------------------ */
|
|
|
|
prevBtn.addEventListener('click', function () { navigate(currentPage - 1); });
|
|
nextBtn.addEventListener('click', function () { navigate(currentPage + 1); });
|
|
|
|
/* ------------------------------------------------------------------
|
|
Keyboard
|
|
------------------------------------------------------------------ */
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
/* Ignore keypresses while focus is in the settings panel. */
|
|
var panel = document.querySelector('.settings-panel');
|
|
if (panel && panel.classList.contains('is-open')) return;
|
|
|
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
navigate(currentPage + 1);
|
|
e.preventDefault();
|
|
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
navigate(currentPage - 1);
|
|
e.preventDefault();
|
|
} else if (e.key === 'Escape') {
|
|
history.back();
|
|
}
|
|
});
|
|
|
|
/* ------------------------------------------------------------------
|
|
Touch swipe — left/right swipe to turn pages
|
|
Threshold: ≥50px horizontal, <30px vertical drift
|
|
------------------------------------------------------------------ */
|
|
|
|
var touchStartX = 0;
|
|
var touchStartY = 0;
|
|
|
|
stage.addEventListener('touchstart', function (e) {
|
|
touchStartX = e.changedTouches[0].clientX;
|
|
touchStartY = e.changedTouches[0].clientY;
|
|
}, { passive: true });
|
|
|
|
stage.addEventListener('touchend', function (e) {
|
|
var dx = e.changedTouches[0].clientX - touchStartX;
|
|
var dy = e.changedTouches[0].clientY - touchStartY;
|
|
if (Math.abs(dx) < 50 || Math.abs(dy) > 30) return;
|
|
if (dx < 0) navigate(currentPage + 1);
|
|
else navigate(currentPage - 1);
|
|
}, { passive: true });
|
|
|
|
/* ------------------------------------------------------------------
|
|
Init — load the starting page
|
|
------------------------------------------------------------------ */
|
|
|
|
navigate(currentPage);
|
|
}());
|