songbook tweaks

This commit is contained in:
gome 2023-11-10 18:13:49 -06:00
parent 278f57bffa
commit 935d89ec17
14 changed files with 152 additions and 74 deletions

View File

@ -16,6 +16,21 @@
<script defer src='js/wotd.js'></script>
<script defer src='js/background.js'></script>
<script defer src='js/gomesong.js'></script>
<script>
fetch("verify.php")
.then(r => {
if (!r.ok) {
throw new Error(`HTTP response ${response.status} ${response.statusText}`);
}
return response.text();
})
.then(user => {
document.getElementById('tagline').innerHTML = `Welcome to gomepage, <b>${user}</b>`
})
.catch(e => {
console.error(e);
});
</script>
</head>
<body>
<header id='header' class='show-nav'>

View File

@ -1,57 +1,116 @@
function make_key(artist, album) {
return `${album}\n${artist}`;
}
async function get_lastfm_entry(artist, album) {
const local_storage_key = make_key(artist, album);
let entry = null;
try {
const json = window.localStorage.getItem(local_storage_key);
entry = JSON.parse(json);
} catch (e) {
console.error(e);
{
// API key is provided to the script via the `data-api-key` attribute on the <script> element
const api_key = document.currentScript.dataset.apiKey;
if (!api_key) {
throw new Error('album-art: provide an API key to the <script> tag with the "data-api-key" attribute');
}
if (entry) {
return entry;
} else {
return fetch(`https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=5460c3381d64e7e9908b9fdfc5559747&format=json&album=${encodeURIComponent(album)}&artist=${encodeURIComponent(artist)}`)
.then(r => r.json())
.then(lastfm => {
if (lastfm.error) {
throw lastfm.error;
} else {
const entry = lastfm.album.image;
window.localStorage.setItem(local_storage_key, JSON.stringify(entry));
return entry;
}
});
}
}
async function fetch_album_art(album, artist, img, small, is_retry) {
try {
const arts = await get_lastfm_entry(artist, album);
const art = small ? arts[1] : arts.find(({size}) => size === 'extralarge') ?? arts.find(({'#text': url}) => url.includes('300x300')) ?? arts[arts.length - 1];
const url = art['#text'];
if (url) {
img.src = url;
img.addEventListener('error', () => {
window.localStorage.removeItem(make_key(artist, album));
if (!is_retry) {
fetch_album_art(album, artist, img, small, true);
const API_URL = 'https://ws.audioscrobbler.com/2.0/';
// utility function to make a key for use in localStorage, unique to a given album/artist query
function make_key(artist, album) {
return `${album}\n${artist}`;
}
// obtain the lastfm entry for the given album, either via fetch
// or from cached entry in localStorage
async function get_lastfm_entry(artist, album) {
const local_storage_key = make_key(artist, album);
let entry = null;
try {
// get the JSON from localStorage and parse it into an object
const json = window.localStorage.getItem(local_storage_key);
entry = JSON.parse(json);
} catch {
// pass (if parse fails, we just refetch)
}
if (entry) {
// successfully pulled entry from localStorage
return entry;
} else {
// query parameters for our request to last.fm API
const search = new URLSearchParams({
method: 'album.getinfo',
format: 'json',
api_key,
artist,
album,
}).toString();
// call last.fm API with these query parameters
return fetch(`${API_URL}?${search}`)
.then(response => response.json())
.then(lastfm => {
if (lastfm.error) {
throw lastfm.error;
} else {
// pull the specific property we care about from the response object (listing of image URLs for the album)
const entry = lastfm.album.image;
// cache it in localStorage for next time this album/artist pair gets called
window.localStorage.setItem(local_storage_key, JSON.stringify(entry));
return entry;
}
});
} else if (small) {
img.src = '../../img/disc.webp';
} else {
img.style.opacity = 0;
}
} catch (e) {
console.error(e);
img.style.opacity = 0;
}
// fetch album art according to artist/album query and put it into the given <img> element
async function fetch_album_art(artist, album, img, is_retry) {
try {
// get the array of image URLs from last.fm or cache
const arts = await get_lastfm_entry(artist, album);
// choose a size appropriate to the <img> element
// fallthrough case is 'mega'
let target_size = 'mega';
let img_size = Math.max(img.width, img.height);
// we want the image to be slightly higher-resolution than the screen size,
// to look OK on high-density displays,
// so we set our thresholds at 0.75 times the respective image size
if (img_size <= 25) { // 34 * 0.75
target_size = 'small';
} else if (img_size <= 48) { // 64 * 0.75
target_size = 'medium';
} else if (img_size <= 130) { // 174 * 0.75
target_size = 'large';
} else if (img_size <= 225) { // 300 * 0.75
target_size = 'extralarge';
} // otherwise 'mega'
// find the corresponding image, or just use the last one if that fails
const art = arts.find(({size}) => size === target_size) ?? arts[arts.length - 1];
const url = art['#text'];
if (url) {
// set URL on <img> element
img.src = url;
// if this image fails to load, the cached last.fm entry is consdered no longer valid
img.addEventListener('error', () => {
// get rid of the bad cache entry
window.localStorage.removeItem(make_key(artist, album));
// we'll retry fetching it one time
if (!is_retry) {
// call this function again, with the retry flag set so we don't recurse forever
fetch_album_art(album, artist, img, true);
} else {
// if we've already retried, set the error on the <img> element
img.dataset.error = 'Failed to load image source after retry';
}
});
} else {
throw new Error('Image unavailable');
}
} catch (error) {
if (error.message === 'Image unavailable') {
// this is our error message, so we want to keep it as-is
throw error;
}
throw new Error('Image fetch failed: ' + error.message);
}
}
// the actual entrypoint of the script
// iterate through all <img> elements with the needed data attributes defined
for (const img of document.querySelectorAll('img[data-album][data-artist]')) {
// run the function to populate this <img> element
// upon error, put the error message in the "data-error" attribute on the <img> element
fetch_album_art(img.dataset.artist, img.dataset.album, img)
.catch(e => img.dataset.error = e.message);
}
}
for (const img of document.querySelectorAll('img[data-album][data-artist]')) {
fetch_album_art(img.dataset.album, img.dataset.artist, img, img.width === 34);
}

View File

@ -5,7 +5,7 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
@ -15,7 +15,7 @@
margin: 0 0 1em 0;
}
</style>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,7 +5,7 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
@ -15,7 +15,7 @@
margin: 0 0 1em 0;
}
</style>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,7 +5,7 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
@ -25,12 +25,16 @@
height: 34px;
padding: 0;
margin: 0;
transition: opacity 300ms;
}
li img[data-error] {
opacity: 0;
}
img[src$="disc.webp"] {
border-color: transparent;
}
</style>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>

View File

@ -5,12 +5,12 @@
<meta charset='utf-8'/>
<meta name='theme-color' content='#efe5d7'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, shrink-to-fit=no'/>
<link rel='icon' type='image/x-icon' href='../favicon.ico'>
<link rel='icon' type='image/x-icon' href='../../favicon.ico'>
<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Lora:ital@0;1&display=swap' rel='stylesheet'>
<link rel='stylesheet' type='text/css' href='../../css/style.css'/>
<script type='module' src='../../js/album-art.js'></script>
<script defer src='../../js/album-art.js' data-api-key='5460c3381d64e7e9908b9fdfc5559747'></script>
</head>
<body>
<header id='header'>