« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/static/client.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/static/client.js')
-rw-r--r--src/static/client.js601
1 files changed, 311 insertions, 290 deletions
diff --git a/src/static/client.js b/src/static/client.js
index 7397735..1ffcb93 100644
--- a/src/static/client.js
+++ b/src/static/client.js
@@ -1,3 +1,5 @@
+/** @format */
+
 // This is the JS file that gets loaded on the client! It's only really used for
 // 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
@@ -5,16 +7,12 @@
 //
 // Upd8: As of 04/02/2021, it's now used for info cards too! Nice.
 
-import {
-    getColors
-} from '../util/colors.js';
+import {getColors} from '../util/colors.js';
 
-import {
-    getArtistNumContributions
-} from '../util/wiki-data.js';
+import {getArtistNumContributions} from '../util/wiki-data.js';
 
-let albumData, artistData, flashData;
-let officialAlbumData, fandomAlbumData, artistNames;
+let albumData, artistData;
+let officialAlbumData, fandomAlbumData;
 
 let ready = false;
 
@@ -23,127 +21,133 @@ let ready = false;
 const language = document.documentElement.getAttribute('lang');
 
 let list;
-if (
-    typeof Intl === 'object' &&
-    typeof Intl.ListFormat === 'function'
-) {
-    const getFormat = type => {
-        const formatter = new Intl.ListFormat(language, {type});
-        return formatter.format.bind(formatter);
-    };
-
-    list = {
-        conjunction: getFormat('conjunction'),
-        disjunction: getFormat('disjunction'),
-        unit: getFormat('unit')
-    };
+if (typeof Intl === 'object' && typeof Intl.ListFormat === 'function') {
+  const getFormat = (type) => {
+    const formatter = new Intl.ListFormat(language, {type});
+    return formatter.format.bind(formatter);
+  };
+
+  list = {
+    conjunction: getFormat('conjunction'),
+    disjunction: getFormat('disjunction'),
+    unit: getFormat('unit'),
+  };
 } else {
-    // Not a gr8 mock we've got going here, 8ut it's *mostly* language-free.
-    // We use the same mock for every list 'cuz we don't have any of the
-    // necessary CLDR info to appropri8tely distinguish 8etween them.
-    const arbitraryMock = array => array.join(', ');
-
-    list = {
-        conjunction: arbitraryMock,
-        disjunction: arbitraryMock,
-        unit: arbitraryMock
-    };
+  // Not a gr8 mock we've got going here, 8ut it's *mostly* language-free.
+  // We use the same mock for every list 'cuz we don't have any of the
+  // necessary CLDR info to appropri8tely distinguish 8etween them.
+  const arbitraryMock = (array) => array.join(', ');
+
+  list = {
+    conjunction: arbitraryMock,
+    disjunction: arbitraryMock,
+    unit: arbitraryMock,
+  };
 }
 
 // Miscellaneous helpers ----------------------------------
 
 function rebase(href, rebaseKey = 'rebaseLocalized') {
-    const relative = (document.documentElement.dataset[rebaseKey] || '.') + '/';
-    if (relative) {
-        return relative + href;
-    } else {
-        return href;
-    }
+  const relative = (document.documentElement.dataset[rebaseKey] || '.') + '/';
+  if (relative) {
+    return relative + href;
+  } else {
+    return href;
+  }
 }
 
 function pick(array) {
-    return array[Math.floor(Math.random() * array.length)];
+  return array[Math.floor(Math.random() * array.length)];
 }
 
 function cssProp(el, key) {
-    return getComputedStyle(el).getPropertyValue(key).trim();
+  return getComputedStyle(el).getPropertyValue(key).trim();
 }
 
 function getRefDirectory(ref) {
-    return ref.split(':')[1];
+  return ref.split(':')[1];
 }
 
 function getAlbum(el) {
-    const directory = cssProp(el, '--album-directory');
-    return albumData.find(album => album.directory === directory);
-}
-
-function getFlash(el) {
-    const directory = cssProp(el, '--flash-directory');
-    return flashData.find(flash => flash.directory === directory);
+  const directory = cssProp(el, '--album-directory');
+  return albumData.find((album) => album.directory === directory);
 }
 
 // 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}`);
-
-function getTrackListAndIndex() {
-    const album = getAlbum(document.body);
-    const directory = cssProp(document.body, '--track-directory');
-    if (!directory && !album) return {};
-    if (!directory) return {list: album.tracks};
-    const trackIndex = album.tracks.findIndex(track => track.directory === directory);
-    return {list: album.tracks, index: trackIndex};
-}
-
-function openRandomTrack() {
-    const { list } = getTrackListAndIndex();
-    if (!list) return;
-    return openTrack(pick(list));
-}
-
-function getFlashListAndIndex() {
-    const list = flashData.filter(flash => !flash.act8r8k)
-    const flash = getFlash(document.body);
-    if (!flash) return {list};
-    const flashIndex = list.indexOf(flash);
-    return {list, index: flashIndex};
-}
+const openAlbum = (d) => rebase(`album/${d}`);
+const openTrack = (d) => rebase(`track/${d}`);
+const openArtist = (d) => rebase(`artist/${d}`);
 
 // TODO: This should also use urlSpec.
 function fetchData(type, directory) {
-    return fetch(rebase(`${type}/${directory}/data.json`, 'rebaseData'))
-        .then(res => res.json());
+  return fetch(rebase(`${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) {
-            evt.preventDefault();
-            return;
-        }
-
-        setTimeout(() => {
-            a.href = rebase('js-disabled');
-        });
-        switch (a.dataset.random) {
-            case 'album': return a.href = openAlbum(pick(albumData).directory);
-            case 'album-in-fandom': return a.href = openAlbum(pick(fandomAlbumData).directory);
-            case 'album-in-official': return a.href = openAlbum(pick(officialAlbumData).directory);
-            case 'track': return a.href = openTrack(getRefDirectory(pick(albumData.map(a => a.tracks).reduce((a, b) => a.concat(b), []))));
-            case 'track-in-album': return a.href = openTrack(getRefDirectory(pick(getAlbum(a).tracks)));
-            case 'track-in-fandom': return a.href = openTrack(getRefDirectory(pick(fandomAlbumData.reduce((acc, album) => acc.concat(album.tracks), []))));
-            case 'track-in-official': return a.href = openTrack(getRefDirectory(pick(officialAlbumData.reduce((acc, album) => acc.concat(album.tracks), []))));
-            case 'artist': return a.href = openArtist(pick(artistData).directory);
-            case 'artist-more-than-one-contrib': return a.href = openArtist(pick(artistData.filter(artist => getArtistNumContributions(artist) > 1)).directory);
-        }
+  a.addEventListener('click', (evt) => {
+    if (!ready) {
+      evt.preventDefault();
+      return;
+    }
+
+    setTimeout(() => {
+      a.href = rebase('js-disabled');
     });
+    switch (a.dataset.random) {
+      case 'album':
+        return (a.href = openAlbum(pick(albumData).directory));
+      case 'album-in-fandom':
+        return (a.href = openAlbum(pick(fandomAlbumData).directory));
+      case 'album-in-official':
+        return (a.href = openAlbum(pick(officialAlbumData).directory));
+      case 'track':
+        return (a.href = openTrack(
+          getRefDirectory(
+            pick(
+              albumData.map((a) => a.tracks).reduce((a, b) => a.concat(b), [])
+            )
+          )
+        ));
+      case 'track-in-album':
+        return (a.href = openTrack(getRefDirectory(pick(getAlbum(a).tracks))));
+      case 'track-in-fandom':
+        return (a.href = openTrack(
+          getRefDirectory(
+            pick(
+              fandomAlbumData.reduce(
+                (acc, album) => acc.concat(album.tracks),
+                []
+              )
+            )
+          )
+        ));
+      case 'track-in-official':
+        return (a.href = openTrack(
+          getRefDirectory(
+            pick(
+              officialAlbumData.reduce(
+                (acc, album) => acc.concat(album.tracks),
+                []
+              )
+            )
+          )
+        ));
+      case 'artist':
+        return (a.href = openArtist(pick(artistData).directory));
+      case 'artist-more-than-one-contrib':
+        return (a.href = openArtist(
+          pick(
+            artistData.filter((artist) => getArtistNumContributions(artist) > 1)
+          ).directory
+        ));
+    }
+  });
 }
 
 const next = document.getElementById('next-button');
@@ -151,38 +155,38 @@ const previous = document.getElementById('previous-button');
 const random = document.getElementById('random-button');
 
 const prependTitle = (el, prepend) => {
-    const existing = el.getAttribute('title');
-    if (existing) {
-        el.setAttribute('title', prepend + ' ' + existing);
-    } else {
-        el.setAttribute('title', prepend);
-    }
+  const existing = el.getAttribute('title');
+  if (existing) {
+    el.setAttribute('title', prepend + ' ' + existing);
+  } else {
+    el.setAttribute('title', prepend);
+  }
 };
 
 if (next) prependTitle(next, '(Shift+N)');
 if (previous) prependTitle(previous, '(Shift+P)');
 if (random) prependTitle(random, '(Shift+R)');
 
-document.addEventListener('keypress', event => {
-    if (event.shiftKey) {
-        if (event.charCode === 'N'.charCodeAt(0)) {
-            if (next) next.click();
-        } else if (event.charCode === 'P'.charCodeAt(0)) {
-            if (previous) previous.click();
-        } else if (event.charCode === 'R'.charCodeAt(0)) {
-            if (random && ready) random.click();
-        }
+document.addEventListener('keypress', (event) => {
+  if (event.shiftKey) {
+    if (event.charCode === 'N'.charCodeAt(0)) {
+      if (next) next.click();
+    } else if (event.charCode === 'P'.charCodeAt(0)) {
+      if (previous) previous.click();
+    } else if (event.charCode === 'R'.charCodeAt(0)) {
+      if (random && ready) random.click();
     }
+  }
 });
 
 for (const reveal of document.querySelectorAll('.reveal')) {
-    reveal.addEventListener('click', event => {
-        if (!reveal.classList.contains('revealed')) {
-            reveal.classList.add('revealed');
-            event.preventDefault();
-            event.stopPropagation();
-        }
-    });
+  reveal.addEventListener('click', (event) => {
+    if (!reveal.classList.contains('revealed')) {
+      reveal.classList.add('revealed');
+      event.preventDefault();
+      event.stopPropagation();
+    }
+  });
 }
 
 const elements1 = document.getElementsByClassName('js-hide-once-data');
@@ -190,20 +194,24 @@ const elements2 = document.getElementsByClassName('js-show-once-data');
 
 for (const element of elements1) element.style.display = 'block';
 
-fetch(rebase('data.json', 'rebaseShared')).then(data => data.json()).then(data => {
+fetch(rebase('data.json', 'rebaseShared'))
+  .then((data) => data.json())
+  .then((data) => {
     albumData = data.albumData;
     artistData = data.artistData;
-    flashData = data.flashData;
 
-    officialAlbumData = albumData.filter(album => album.groups.includes('group:official'));
-    fandomAlbumData = albumData.filter(album => !album.groups.includes('group:official'));
-    artistNames = artistData.filter(artist => !artist.alias).map(artist => artist.name);
+    officialAlbumData = albumData.filter((album) =>
+      album.groups.includes('group:official')
+    );
+    fandomAlbumData = albumData.filter(
+      (album) => !album.groups.includes('group:official')
+    );
 
     for (const element of elements1) element.style.display = 'none';
     for (const element of elements2) element.style.display = 'block';
 
     ready = true;
-});
+  });
 
 // Data & info card ---------------------------------------
 
@@ -216,197 +224,210 @@ let fastHover = false;
 let endFastHoverTimeout = null;
 
 function colorLink(a, color) {
-    if (color) {
-        const { primary, dim } = getColors(color);
-        a.style.setProperty('--primary-color', primary);
-        a.style.setProperty('--dim-color', dim);
-    }
+  if (color) {
+    const {primary, dim} = getColors(color);
+    a.style.setProperty('--primary-color', primary);
+    a.style.setProperty('--dim-color', dim);
+  }
 }
 
 function link(a, type, {name, directory, color}) {
-    colorLink(a, color);
-    a.innerText = name
-    a.href = getLinkHref(type, directory);
+  colorLink(a, color);
+  a.innerText = name;
+  a.href = getLinkHref(type, directory);
 }
 
 function joinElements(type, elements) {
-    // We can't use the Intl APIs with elements, 8ecuase it only oper8tes on
-    // strings. So instead, we'll pass the element's outer HTML's (which means
-    // the entire HTML of that element).
-    //
-    // That does mean this function returns a string, so always 8e sure to
-    // set innerHTML when using it (not appendChild).
-
-    return list[type](elements.map(el => el.outerHTML));
+  // We can't use the Intl APIs with elements, 8ecuase it only oper8tes on
+  // strings. So instead, we'll pass the element's outer HTML's (which means
+  // the entire HTML of that element).
+  //
+  // That does mean this function returns a string, so always 8e sure to
+  // set innerHTML when using it (not appendChild).
+
+  return list[type](elements.map((el) => el.outerHTML));
 }
 
 const infoCard = (() => {
-    const container = document.getElementById('info-card-container');
-
-    let cancelShow = false;
-    let hideTimeout = null;
-    let showing = false;
-
-    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;
-            }
-
-            showing = true;
-
-            const rect = target.getBoundingClientRect();
-
-            container.style.setProperty('--primary-color', data.color);
-
-            container.style.top = window.scrollY + rect.bottom + 'px';
-            container.style.left = window.scrollX + rect.left + 'px';
-
-            // Use a short timeout to let a currently hidden (or not yet shown)
-            // info card teleport to the position set a8ove. (If it's currently
-            // shown, it'll transition to that position.)
-            setTimeout(() => {
-                container.classList.remove('hide');
-                container.classList.add('show');
-            }, 50);
-
-            // 8asic details.
-
-            const nameLink = container.querySelector('.info-card-name a');
-            link(nameLink, 'track', data);
-
-            const albumLink = container.querySelector('.info-card-album a');
-            link(albumLink, 'album', data.album);
-
-            const artistSpan = container.querySelector('.info-card-artists span');
-            artistSpan.innerHTML = joinElements('conjunction', data.artists.map(({ artist }) => {
-                const a = document.createElement('a');
-                a.href = getLinkHref('artist', artist.directory);
-                a.innerText = artist.name;
-                return a;
-            }));
-
-            const coverArtistParagraph = container.querySelector('.info-card-cover-artists');
-            const coverArtistSpan = coverArtistParagraph.querySelector('span');
-            if (data.coverArtists.length) {
-                coverArtistParagraph.style.display = 'block';
-                coverArtistSpan.innerHTML = joinElements('conjunction', data.coverArtists.map(({ artist }) => {
-                    const a = document.createElement('a');
-                    a.href = getLinkHref('artist', artist.directory);
-                    a.innerText = artist.name;
-                    return a;
-                }));
-            } else {
-                coverArtistParagraph.style.display = 'none';
-            }
-
-            // Cover art.
-
-            const [ containerNoReveal, containerReveal ] = [
-                container.querySelector('.info-card-art-container.no-reveal'),
-                container.querySelector('.info-card-art-container.reveal')
-            ];
-
-            const [ containerShow, containerHide ] = (data.cover.warnings.length
-                ? [containerReveal, containerNoReveal]
-                : [containerNoReveal, containerReveal]);
-
-            containerHide.style.display = 'none';
-            containerShow.style.display = 'block';
-
-            const img = containerShow.querySelector('.info-card-art');
-            img.src = rebase(data.cover.paths.small, 'rebaseMedia');
-
-            const imgLink = containerShow.querySelector('a');
-            colorLink(imgLink, data.color);
-            imgLink.href = rebase(data.cover.paths.original, 'rebaseMedia');
-
-            if (containerShow === containerReveal) {
-                const cw = containerShow.querySelector('.info-card-art-warnings');
-                cw.innerText = list.unit(data.cover.warnings);
-
-                const reveal = containerShow.querySelector('.reveal');
-                reveal.classList.remove('revealed');
-            }
-        });
-    }
-
-    function hide() {
-        container.classList.remove('show');
-        container.classList.add('hide');
-        cancelShow = true;
-        showing = false;
-    }
-
-    function readyHide() {
-        if (!hideTimeout && showing) {
-            hideTimeout = setTimeout(hide, HIDE_HOVER_DELAY);
-        }
+  const container = document.getElementById('info-card-container');
+
+  let cancelShow = false;
+  let hideTimeout = null;
+  let showing = false;
+
+  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;
+      }
+
+      showing = true;
+
+      const rect = target.getBoundingClientRect();
+
+      container.style.setProperty('--primary-color', data.color);
+
+      container.style.top = window.scrollY + rect.bottom + 'px';
+      container.style.left = window.scrollX + rect.left + 'px';
+
+      // Use a short timeout to let a currently hidden (or not yet shown)
+      // info card teleport to the position set a8ove. (If it's currently
+      // shown, it'll transition to that position.)
+      setTimeout(() => {
+        container.classList.remove('hide');
+        container.classList.add('show');
+      }, 50);
+
+      // 8asic details.
+
+      const nameLink = container.querySelector('.info-card-name a');
+      link(nameLink, 'track', data);
+
+      const albumLink = container.querySelector('.info-card-album a');
+      link(albumLink, 'album', data.album);
+
+      const artistSpan = container.querySelector('.info-card-artists span');
+      artistSpan.innerHTML = joinElements(
+        'conjunction',
+        data.artists.map(({artist}) => {
+          const a = document.createElement('a');
+          a.href = getLinkHref('artist', artist.directory);
+          a.innerText = artist.name;
+          return a;
+        })
+      );
+
+      const coverArtistParagraph = container.querySelector(
+        '.info-card-cover-artists'
+      );
+      const coverArtistSpan = coverArtistParagraph.querySelector('span');
+      if (data.coverArtists.length) {
+        coverArtistParagraph.style.display = 'block';
+        coverArtistSpan.innerHTML = joinElements(
+          'conjunction',
+          data.coverArtists.map(({artist}) => {
+            const a = document.createElement('a');
+            a.href = getLinkHref('artist', artist.directory);
+            a.innerText = artist.name;
+            return a;
+          })
+        );
+      } else {
+        coverArtistParagraph.style.display = 'none';
+      }
+
+      // Cover art.
+
+      const [containerNoReveal, containerReveal] = [
+        container.querySelector('.info-card-art-container.no-reveal'),
+        container.querySelector('.info-card-art-container.reveal'),
+      ];
+
+      const [containerShow, containerHide] = data.cover.warnings.length
+        ? [containerReveal, containerNoReveal]
+        : [containerNoReveal, containerReveal];
+
+      containerHide.style.display = 'none';
+      containerShow.style.display = 'block';
+
+      const img = containerShow.querySelector('.info-card-art');
+      img.src = rebase(data.cover.paths.small, 'rebaseMedia');
+
+      const imgLink = containerShow.querySelector('a');
+      colorLink(imgLink, data.color);
+      imgLink.href = rebase(data.cover.paths.original, 'rebaseMedia');
+
+      if (containerShow === containerReveal) {
+        const cw = containerShow.querySelector('.info-card-art-warnings');
+        cw.innerText = list.unit(data.cover.warnings);
+
+        const reveal = containerShow.querySelector('.reveal');
+        reveal.classList.remove('revealed');
+      }
+    });
+  }
+
+  function hide() {
+    container.classList.remove('show');
+    container.classList.add('hide');
+    cancelShow = true;
+    showing = false;
+  }
+
+  function readyHide() {
+    if (!hideTimeout && showing) {
+      hideTimeout = setTimeout(hide, HIDE_HOVER_DELAY);
     }
+  }
 
-    function cancelHide() {
-        if (hideTimeout) {
-            clearTimeout(hideTimeout);
-            hideTimeout = null;
-        }
+  function cancelHide() {
+    if (hideTimeout) {
+      clearTimeout(hideTimeout);
+      hideTimeout = null;
     }
-
-    return {
-        show,
-        hide,
-        readyHide,
-        cancelHide
-    };
+  }
+
+  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);
+  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;
+      clearTimeout(endFastHoverTimeout);
+      endFastHoverTimeout = null;
 
-            infoCard.cancelHide();
-        },
+      infoCard.cancelHide();
+    },
 
-        mouseleave(evt) {
-            clearTimeout(hoverTimeout);
+    mouseleave() {
+      clearTimeout(hoverTimeout);
 
-            if (fastHover && !endFastHoverTimeout) {
-                endFastHoverTimeout = setTimeout(() => {
-                    endFastHoverTimeout = null;
-                    fastHover = false;
-                }, END_FAST_HOVER_DELAY);
-            }
+      if (fastHover && !endFastHoverTimeout) {
+        endFastHoverTimeout = setTimeout(() => {
+          endFastHoverTimeout = null;
+          fastHover = false;
+        }, END_FAST_HOVER_DELAY);
+      }
 
-            infoCard.readyHide();
-        }
-    };
+      infoCard.readyHide();
+    },
+  };
 }
 
 const infoCardLinkHandlers = {
-    track: makeInfoCardLinkHandlers('track')
+  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);
-        }
+  for (const a of document.querySelectorAll(`a[data-${type}]`)) {
+    for (const [eventName, handler] of Object.entries(
+      infoCardLinkHandlers[type]
+    )) {
+      a.addEventListener(eventName, handler);
     }
+  }
 }
 
 // Info cards are disa8led for now since they aren't quite ready for release,
@@ -415,5 +436,5 @@ function addInfoCardLinkHandlers(type) {
 //     localStorage.tryInfoCards = true;
 //
 if (localStorage.tryInfoCards) {
-    addInfoCardLinkHandlers('track');
+  addInfoCardLinkHandlers('track');
 }