diff options
-rw-r--r-- | static/client.js | 154 | ||||
-rw-r--r-- | static/site.css | 58 | ||||
-rw-r--r-- | strings-default.json | 1 | ||||
-rwxr-xr-x | upd8.js | 10 |
4 files changed, 176 insertions, 47 deletions
diff --git a/static/client.js b/static/client.js index 2b58ccb6..b7aa27b0 100644 --- a/static/client.js +++ b/static/client.js @@ -2,6 +2,8 @@ // the random track feature right now - the idea is we only use it for stuff // that cannot 8e done at static-site compile time, 8y its fundamentally // ephemeral nature. +// +// Upd8: As of 04/02/2021, it's now used for info cards too! Nice. 'use strict'; @@ -10,8 +12,10 @@ let officialAlbumData, fandomAlbumData, artistNames; let ready = false; +// Miscellaneous helpers ---------------------------------- + function rebase(href, rebaseKey = 'rebaseLocalized') { - const relative = document.documentElement.dataset[rebaseKey]; + const relative = document.documentElement.dataset[rebaseKey] + '/'; if (relative) { return relative + href; } else { @@ -43,25 +47,12 @@ function getFlash(el) { // TODO: These should pro8a8ly access some shared urlSpec path. We'd need to // separ8te the tooling around that into common-shared code too. +const getLinkHref = (type, directory) => rebase(`${type}/${directory}`); const openAlbum = d => rebase(`album/${d}`); const openTrack = d => rebase(`track/${d}`); const openArtist = d => rebase(`artist/${d}`); const openFlash = d => rebase(`flash/${d}`); -/* i implemented these functions but we dont actually use them anywhere lol -function isFlashPage() { - return !!cssProp(document.body, '--flash-directory'); -} - -function isTrackOrAlbumPage() { - return !!cssProp(document.body, '--album-directory'); -} - -function isTrackPage() { - return !!cssProp(document.body, '--track-directory'); -} -*/ - function getTrackListAndIndex() { const album = getAlbum(document.body); const directory = cssProp(document.body, '--track-directory'); @@ -85,6 +76,14 @@ function getFlashListAndIndex() { return {list, index: flashIndex}; } +// TODO: This should also use urlSpec. +function fetchData(type, directory) { + return fetch(rebase(`data/${type}/${directory}/data.json`, 'rebaseData')) + .then(res => res.json()); +} + +// JS-based links ----------------------------------------- + for (const a of document.body.querySelectorAll('[data-random]')) { a.addEventListener('click', evt => { if (!ready) { @@ -167,3 +166,128 @@ fetch(rebase('data.json', 'rebaseShared')).then(data => data.json()).then(data = ready = true; }); + +// Data & info card --------------------------------------- + +const NORMAL_HOVER_INFO_DELAY = 750; +const FAST_HOVER_INFO_DELAY = 250; +const END_FAST_HOVER_DELAY = 500; +const HIDE_HOVER_DELAY = 250; + +let fastHover = false; +let endFastHoverTimeout = null; + +function link(a, type, {name, directory, color}) { + if (color) { + a.style.setProperty('--primary-color', color); + } + + a.innerText = name + a.href = getLinkHref(type, directory); +} + +const infoCard = (() => { + const container = document.getElementById('info-card-container'); + + let cancelShow = false; + let hideTimeout = null; + + container.addEventListener('mouseenter', cancelHide); + container.addEventListener('mouseleave', readyHide); + + function show(type, target) { + cancelShow = false; + + fetchData(type, target.dataset[type]).then(data => { + // Manual DOM 'cuz we're laaaazy. + + if (cancelShow) { + return; + } + + const rect = target.getBoundingClientRect(); + + container.style.setProperty('--primary-color', data.color); + + container.classList.add('shown'); + container.style.top = window.scrollY + rect.bottom + 'px'; + container.style.left = window.scrollX + rect.left + 'px'; + + const nameLink = container.querySelector('.info-card-name a'); + link(nameLink, 'track', data); + + const albumLink = container.querySelector('.info-card-album a'); + link(albumLink, 'album', data.links.album); + }); + } + + function hide() { + container.classList.remove('shown'); + cancelShow = true; + } + + function readyHide() { + if (!hideTimeout) { + hideTimeout = setTimeout(hide, HIDE_HOVER_DELAY); + } + } + + function cancelHide() { + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + } + + return { + show, + hide, + readyHide, + cancelHide + }; +})(); + +function makeInfoCardLinkHandlers(type) { + let hoverTimeout = null; + + return { + mouseenter(evt) { + hoverTimeout = setTimeout(() => { + fastHover = true; + infoCard.show(type, evt.target); + }, fastHover ? FAST_HOVER_INFO_DELAY : NORMAL_HOVER_INFO_DELAY); + + clearTimeout(endFastHoverTimeout); + endFastHoverTimeout = null; + + infoCard.cancelHide(); + }, + + mouseleave(evt) { + clearTimeout(hoverTimeout); + + if (fastHover && !endFastHoverTimeout) { + endFastHoverTimeout = setTimeout(() => { + endFastHoverTimeout = null; + fastHover = false; + }, END_FAST_HOVER_DELAY); + } + + infoCard.readyHide(); + } + }; +} + +const infoCardLinkHandlers = { + track: makeInfoCardLinkHandlers('track') +}; + +function addInfoCardLinkHandlers(type) { + for (const a of document.querySelectorAll(`a[data-${type}]`)) { + for (const [ eventName, handler ] of Object.entries(infoCardLinkHandlers[type])) { + a.addEventListener(eventName, handler); + } + } +} + +addInfoCardLinkHandlers('track'); diff --git a/static/site.css b/static/site.css index 10ba090f..c62b4f68 100644 --- a/static/site.css +++ b/static/site.css @@ -335,7 +335,7 @@ footer > :last-child { .sidebar article h2, .news-index h2 { - border-bottom: 1px dotted white; + border-bottom: 1px dotted; } .sidebar article h2 time, @@ -701,46 +701,40 @@ li > ul { margin-top: 5px; } -.new { - animation: new 1s infinite; +#info-card-container { + position: absolute; + display: none; + + margin-right: 10px; } -@keyframes new { - 0% { - color: #bbdd00; - } +#info-card-container.shown { + display: block; +} - 50% { - color: #eeff22; - } +.info-card { + background-color: rgba(0, 0, 0, 0.8); + border: 1px dotted var(--primary-color); + border-radius: 3px; + box-shadow: 0 5px 5px black; - 100% { - color: #bbdd00; - } + padding: 5px; + font-size: 0.9em; } -/* fake :P */ -.loading::after { - content: '.'; - animation: loading 6s infinite; +.info-card-name { + font-size: 1em; + border-bottom: 1px dotted; + margin: 0; } -@keyframes loading { - 0 { - content: '.'; - } - - 33% { - content: '..'; - } - - 66% { - content: '...'; - } +.info-card p { + margin-top: 0.25em; + margin-bottom: 0.25em; +} - 100% { - content: '.'; - } +.info-card p:last-child { + margin-bottom: 0; } @media (max-width: 900px) { diff --git a/strings-default.json b/strings-default.json index e9dbf293..aa98eb9a 100644 --- a/strings-default.json +++ b/strings-default.json @@ -71,6 +71,7 @@ "count.duration.approximate": "~{DURATION}", "count.duration.missing": "_:__", "releaseInfo.by": "By {ARTISTS}.", + "releaseInfo.from": "From {ALBUM}.", "releaseInfo.coverArtBy": "Cover art by {ARTISTS}.", "releaseInfo.wallpaperArtBy": "Wallpaper art by {ARTISTS}.", "releaseInfo.bannerArtBy": "Banner art by {ARTISTS}.", diff --git a/upd8.js b/upd8.js index fe1601a5..2ca90fe3 100755 --- a/upd8.js +++ b/upd8.js @@ -2387,6 +2387,15 @@ writePage.html = (pageFn, {paths, strings, to}) => { footerHTML ].filter(Boolean).join('\n'); + const infoCardHTML = fixWS` + <div id="info-card-container"> + <div class="info-card"> + <h1 class="info-card-name"><a></a></h1> + <p class="info-card-album">${strings('releaseInfo.from', {album: '<a></a>'})}</p> + </div> + </div> + `; + return filterEmptyLines(fixWS` <!DOCTYPE html> <html ${attributes({ @@ -2429,6 +2438,7 @@ writePage.html = (pageFn, {paths, strings, to}) => { `} ${layoutHTML} </div> + ${infoCardHTML} <script src="${to('shared.commonFile', `common.js?${CACHEBUST}`)}"></script> <script src="${to('shared.staticFile', `client.js?${CACHEBUST}`)}"></script> </body> |