Fix mobile view Bugs and add versions to static files for cache breaking
This commit is contained in:
parent
13770f21e3
commit
7760ae4f49
11
.coveragerc
11
.coveragerc
@ -1,11 +0,0 @@
|
|||||||
[run]
|
|
||||||
branch = True
|
|
||||||
relative_files = True
|
|
||||||
source = tools/python
|
|
||||||
|
|
||||||
[report]
|
|
||||||
show_missing = True
|
|
||||||
skip_empty = True
|
|
||||||
|
|
||||||
[xml]
|
|
||||||
output = coverage/python/coverage.xml
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "wegwichtel-next",
|
"name": "wegwichtel-next",
|
||||||
"version": "0.12.10",
|
"version": "0.12.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wegwichtel-next",
|
"name": "wegwichtel-next",
|
||||||
"version": "0.12.10",
|
"version": "0.12.11",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@file-type/av": "0.2.0",
|
"@file-type/av": "0.2.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wegwichtel-next",
|
"name": "wegwichtel-next",
|
||||||
"version": "0.12.10",
|
"version": "0.12.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "GPS-gestützte Lern- und Wanderwege mit GPX, POIs, Bildern und Audioansagen.",
|
"description": "GPS-gestützte Lern- und Wanderwege mit GPX, POIs, Bildern und Audioansagen.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -327,7 +327,7 @@ button,
|
|||||||
margin: 0 0 1rem;
|
margin: 0 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#route-page fieldset button {
|
#route-page fieldset button.ui-button {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -341,18 +341,18 @@ button,
|
|||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
#route-page fieldset button:focus-visible {
|
#route-page fieldset button.ui-button:focus-visible {
|
||||||
outline: .2rem solid var(--focus);
|
outline: .2rem solid var(--focus);
|
||||||
outline-offset: .15rem;
|
outline-offset: .15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#route-page fieldset button:disabled {
|
#route-page fieldset button.ui-button:disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
filter: grayscale(.65);
|
filter: grayscale(.65);
|
||||||
opacity: .42;
|
opacity: .42;
|
||||||
}
|
}
|
||||||
|
|
||||||
#route-page fieldset img {
|
#route-page fieldset button.ui-button img {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -397,17 +397,18 @@ button,
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#slideshow img {
|
#slide-image {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: min(55vh, 28rem);
|
||||||
height: min(55dvh, 28rem);
|
height: min(55dvh, 28rem);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: #111;
|
background: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
#slideshow > fieldset {
|
#slideshow > fieldset {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-flow: row nowrap;
|
grid-template-columns: var(--icon-button-size) minmax(0, 1fr) var(--icon-button-size);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -419,8 +420,6 @@ button,
|
|||||||
|
|
||||||
#slideshow > fieldset > button.ui-button {
|
#slideshow > fieldset > button.ui-button {
|
||||||
display: grid;
|
display: grid;
|
||||||
flex: 0 0 var(--icon-button-size);
|
|
||||||
align-self: center;
|
|
||||||
place-items: center;
|
place-items: center;
|
||||||
width: var(--icon-button-size);
|
width: var(--icon-button-size);
|
||||||
height: var(--icon-button-size);
|
height: var(--icon-button-size);
|
||||||
@ -438,7 +437,6 @@ button,
|
|||||||
|
|
||||||
#slideshow > fieldset > div {
|
#slideshow > fieldset > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 0;
|
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: var(--icon-button-size);
|
min-height: var(--icon-button-size);
|
||||||
@ -466,10 +464,16 @@ button,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#poi-audio {
|
#poi-audio {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#poi-audio[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#navigation-footer {
|
#navigation-footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<meta name="theme-color" content="#315d3a">
|
<meta name="theme-color" content="#315d3a">
|
||||||
<title>Wegwichtel</title>
|
<title>Wegwichtel</title>
|
||||||
<link rel="stylesheet" href="css/bootstrap.css">
|
<link rel="stylesheet" href="css/bootstrap.css?v=0.12.11">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="bootstrap-screen" aria-live="polite" aria-busy="true">
|
<div id="bootstrap-screen" aria-live="polite" aria-busy="true">
|
||||||
@ -84,13 +84,13 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Routensteuerung</legend>
|
<legend>Routensteuerung</legend>
|
||||||
<button id="start-route" type="button" aria-label="Route starten" title="Route starten">
|
<button id="start-route" type="button" aria-label="Route starten" title="Route starten">
|
||||||
<img src="images/icons/play.svg" alt="">
|
<img src="images/icons/play.svg" alt="Route starten">
|
||||||
</button>
|
</button>
|
||||||
<button id="pause-route" type="button" aria-label="Route pausieren" title="Route pausieren" disabled>
|
<button id="pause-route" type="button" aria-label="Route pausieren" title="Route pausieren" disabled>
|
||||||
<img src="images/icons/pause.svg" alt="">
|
<img src="images/icons/pause.svg" alt="Route pausieren">
|
||||||
</button>
|
</button>
|
||||||
<button id="stop-route" type="button" aria-label="Route beenden" title="Route beenden" disabled>
|
<button id="stop-route" type="button" aria-label="Route beenden" title="Route beenden" disabled>
|
||||||
<img src="images/icons/stop.svg" alt="">
|
<img src="images/icons/stop.svg" alt="Route beenden">
|
||||||
</button>
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p id="tracking-status" class="notice" hidden></p>
|
<p id="tracking-status" class="notice" hidden></p>
|
||||||
@ -117,20 +117,20 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Bildnavigation</legend>
|
<legend>Bildnavigation</legend>
|
||||||
<button id="slide-prev" type="button" aria-label="Vorheriges Bild" title="Vorheriges Bild">
|
<button id="slide-prev" type="button" aria-label="Vorheriges Bild" title="Vorheriges Bild">
|
||||||
<img src="images/icons/chevron-left.svg" alt="">
|
<img src="images/icons/chevron-left.svg" alt="Vorheriges Bild">
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<p id="slide-caption"></p>
|
<p id="slide-caption"></p>
|
||||||
<span id="slide-position">0 / 0</span>
|
<span id="slide-position">0 / 0</span>
|
||||||
</div>
|
</div>
|
||||||
<button id="slide-next" type="button" aria-label="Nächstes Bild" title="Nächstes Bild">
|
<button id="slide-next" type="button" aria-label="Nächstes Bild" title="Nächstes Bild">
|
||||||
<img src="images/icons/chevron-right.svg" alt="">
|
<img src="images/icons/chevron-right.svg" alt="Nächstes Bild">
|
||||||
</button>
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="poi-description"></p>
|
<p id="poi-description"></p>
|
||||||
<audio id="poi-audio" controls preload="auto" hidden></audio>
|
<audio id="poi-audio" controls preload="metadata" aria-label="Audiowiedergabe zur Station" hidden>Audio kann auf diesem Gerät nicht wiedergegeben werden.</audio>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -145,6 +145,6 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="js/bootstrap-loader.js"></script>
|
<script src="js/bootstrap-loader.js?v=0.12.11"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,12 +5,18 @@
|
|||||||
return $('#poi-audio')[0];
|
return $('#poi-audio')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mediaUrl(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
const separator = url.includes('?') ? '&' : '?';
|
||||||
|
return `${url}${separator}v=${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
function unload() {
|
function unload() {
|
||||||
const audio = element();
|
const audio = element();
|
||||||
audio.pause();
|
audio.pause();
|
||||||
audio.removeAttribute('src');
|
audio.removeAttribute('src');
|
||||||
audio.load();
|
audio.load();
|
||||||
$(audio).prop('hidden', true);
|
$(audio).attr('hidden', '').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
ns.AudioPlayer = {
|
ns.AudioPlayer = {
|
||||||
@ -19,8 +25,8 @@
|
|||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const audio = element();
|
const audio = element();
|
||||||
audio.src = url;
|
audio.src = mediaUrl(url);
|
||||||
$(audio).prop('hidden', false);
|
$(audio).removeAttr('hidden').css('display', 'block');
|
||||||
audio.load();
|
audio.load();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
8
public/js/bootstrap-loader.js
vendored
8
public/js/bootstrap-loader.js
vendored
@ -1,12 +1,14 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const versions = Object.freeze({ jquery: '4.0.0', jqueryUi: '1.14.2' });
|
const versions = Object.freeze({ app: '0.12.11', jquery: '4.0.0', jqueryUi: '1.14.2' });
|
||||||
const steps = ['jquery', 'jquery-ui', 'modules', 'server', 'routes', 'location'];
|
const steps = ['jquery', 'jquery-ui', 'modules', 'server', 'routes', 'location'];
|
||||||
const progress = document.getElementById('bootstrap-progress');
|
const progress = document.getElementById('bootstrap-progress');
|
||||||
const errorBox = document.getElementById('bootstrap-error');
|
const errorBox = document.getElementById('bootstrap-error');
|
||||||
const retry = document.getElementById('bootstrap-retry');
|
const retry = document.getElementById('bootstrap-retry');
|
||||||
|
|
||||||
|
const versioned = path => `${path}${path.includes('?') ? '&' : '?'}v=${encodeURIComponent(versions.app)}`;
|
||||||
|
|
||||||
function mark(step, state) {
|
function mark(step, state) {
|
||||||
const item = document.querySelector(`[data-step="${step}"]`);
|
const item = document.querySelector(`[data-step="${step}"]`);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
@ -18,7 +20,7 @@
|
|||||||
function loadScript(src, test) {
|
function loadScript(src, test) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = src;
|
script.src = versioned(src);
|
||||||
script.async = false;
|
script.async = false;
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
if (test && !test()) reject(new Error(`Selbsttest fehlgeschlagen: ${src}`));
|
if (test && !test()) reject(new Error(`Selbsttest fehlgeschlagen: ${src}`));
|
||||||
@ -33,7 +35,7 @@
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const link = document.createElement('link');
|
const link = document.createElement('link');
|
||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet';
|
||||||
link.href = href;
|
link.href = versioned(href);
|
||||||
link.onload = resolve;
|
link.onload = resolve;
|
||||||
link.onerror = () => reject(new Error(`Stylesheet konnte nicht geladen werden: ${href}`));
|
link.onerror = () => reject(new Error(`Stylesheet konnte nicht geladen werden: ${href}`));
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
|
|||||||
@ -5,6 +5,12 @@
|
|||||||
let index = 0;
|
let index = 0;
|
||||||
let swipe = null;
|
let swipe = null;
|
||||||
|
|
||||||
|
function mediaUrl(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
const separator = url.includes('?') ? '&' : '?';
|
||||||
|
return `${url}${separator}v=${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
function activate(selector, handler) {
|
function activate(selector, handler) {
|
||||||
$(document)
|
$(document)
|
||||||
.on('pointerup', selector, function (event) {
|
.on('pointerup', selector, function (event) {
|
||||||
@ -33,8 +39,12 @@
|
|||||||
|
|
||||||
const image = images[index];
|
const image = images[index];
|
||||||
$('#slide-image')
|
$('#slide-image')
|
||||||
.attr('src', image.url)
|
.attr('src', mediaUrl(image.url))
|
||||||
.attr('alt', image.caption || 'Bild zur Station');
|
.attr('alt', image.caption || 'Bild zur Station')
|
||||||
|
.off('error.wegwichtel')
|
||||||
|
.on('error.wegwichtel', function () {
|
||||||
|
$(this).removeAttr('src').attr('alt', 'Bild konnte nicht geladen werden');
|
||||||
|
});
|
||||||
$('#slide-caption').text(image.caption || '');
|
$('#slide-caption').text(image.caption || '');
|
||||||
$('#slide-position').text(`${index + 1} / ${images.length}`);
|
$('#slide-position').text(`${index + 1} / ${images.length}`);
|
||||||
refreshButton('#slide-prev', images.length < 2);
|
refreshButton('#slide-prev', images.length < 2);
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
coverage==7.13.3
|
|
||||||
14
server.js
14
server.js
@ -18,7 +18,19 @@ app.use(express.json({
|
|||||||
}));
|
}));
|
||||||
app.use(express.urlencoded({ extended: true, limit: '2mb' }));
|
app.use(express.urlencoded({ extended: true, limit: '2mb' }));
|
||||||
app.use('/api', createApiRouter(db));
|
app.use('/api', createApiRouter(db));
|
||||||
app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
|
app.use(express.static(path.join(__dirname, 'public'), {
|
||||||
|
extensions: ['html'],
|
||||||
|
etag: true,
|
||||||
|
lastModified: true,
|
||||||
|
maxAge: 0,
|
||||||
|
setHeaders(res, filePath) {
|
||||||
|
if (filePath.endsWith('.html')) {
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
|
||||||
|
}
|
||||||
|
}));
|
||||||
app.use(notFoundHandler);
|
app.use(notFoundHandler);
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
|||||||
@ -20,11 +20,12 @@ function sendFileResource(res, file, contentType = null) {
|
|||||||
if (contentType) res.type(contentType);
|
if (contentType) res.type(contentType);
|
||||||
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`);
|
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`);
|
||||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate');
|
||||||
return res.sendFile(file.absolutePath, {
|
return res.sendFile(file.absolutePath, {
|
||||||
acceptRanges: true,
|
acceptRanges: true,
|
||||||
cacheControl: true,
|
cacheControl: true,
|
||||||
immutable: false,
|
immutable: false,
|
||||||
maxAge: '1h'
|
maxAge: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -168,7 +168,9 @@ test('reaching a POI preloads audio without autoplay and uses the vibration wrap
|
|||||||
assert.doesNotMatch(app, /navigator\.vibrate/);
|
assert.doesNotMatch(app, /navigator\.vibrate/);
|
||||||
assert.match(app, /function stopRouteSensors\(\)[\s\S]*ns\.Vibration\.stop\(\)/);
|
assert.match(app, /function stopRouteSensors\(\)[\s\S]*ns\.Vibration\.stop\(\)/);
|
||||||
assert.match(audio, /audio\.load\(\)/);
|
assert.match(audio, /audio\.load\(\)/);
|
||||||
assert.match(html, /<audio id="poi-audio" controls preload="auto" hidden>/);
|
assert.match(html, /<audio id="poi-audio" controls preload="metadata" aria-label="Audiowiedergabe zur Station" hidden>/);
|
||||||
|
assert.match(audio, /removeAttr\('hidden'\)/);
|
||||||
|
assert.match(audio, /Date\.now\(\)/);
|
||||||
assert.match(loader, /'vibration'/);
|
assert.match(loader, /'vibration'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -182,12 +184,16 @@ test('POI page uses icon-based back and slideshow controls in a single responsiv
|
|||||||
assert.match(html, /id="slide-prev"[\s\S]*images\/icons\/chevron-left\.svg/);
|
assert.match(html, /id="slide-prev"[\s\S]*images\/icons\/chevron-left\.svg/);
|
||||||
assert.match(html, /id="slide-next"[\s\S]*images\/icons\/chevron-right\.svg/);
|
assert.match(html, /id="slide-next"[\s\S]*images\/icons\/chevron-right\.svg/);
|
||||||
assert.match(html, /<fieldset>[\s\S]*<legend>Bildnavigation<\/legend>/);
|
assert.match(html, /<fieldset>[\s\S]*<legend>Bildnavigation<\/legend>/);
|
||||||
assert.match(css, /#slideshow > fieldset[\s\S]*display:\s*flex/);
|
assert.match(css, /#slideshow > fieldset[\s\S]*display:\s*grid/);
|
||||||
assert.match(css, /#slideshow > fieldset[\s\S]*flex-flow:\s*row nowrap/);
|
assert.match(css, /grid-template-columns:\s*var\(--icon-button-size\) minmax\(0, 1fr\) var\(--icon-button-size\)/);
|
||||||
assert.match(css, /#slideshow > fieldset[\s\S]*align-items:\s*center/);
|
assert.match(css, /#slideshow > fieldset[\s\S]*align-items:\s*center/);
|
||||||
assert.match(css, /#slideshow > fieldset > button\.ui-button[\s\S]*flex:\s*0 0 var\(--icon-button-size\)/);
|
assert.match(css, /#slideshow > fieldset > button\.ui-button[\s\S]*width:\s*var\(--icon-button-size\)/);
|
||||||
assert.match(css, /#slideshow > fieldset > div[\s\S]*flex:\s*1 1 0/);
|
assert.match(css, /#slideshow > fieldset > div[\s\S]*min-width:\s*0/);
|
||||||
assert.match(css, /#slideshow > fieldset > div[\s\S]*justify-content:\s*center/);
|
assert.match(css, /#slideshow > fieldset > div[\s\S]*justify-content:\s*center/);
|
||||||
assert.match(css, /#slide-caption[\s\S]*overflow-wrap:\s*anywhere/);
|
assert.match(css, /#slide-caption[\s\S]*overflow-wrap:\s*anywhere/);
|
||||||
|
assert.match(css, /#slide-image[\s\S]*object-fit:\s*contain/);
|
||||||
|
assert.doesNotMatch(css, /#slideshow img\s*\{/);
|
||||||
|
assert.match(html, /images\/icons\/play\.svg" alt="Route starten"/);
|
||||||
|
assert.match(html, /images\/icons\/chevron-left\.svg" alt="Vorheriges Bild"/);
|
||||||
assert.doesNotMatch(loader, /controlgroup/);
|
assert.doesNotMatch(loader, /controlgroup/);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user