« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/listing-spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/listing-spec.js')
-rw-r--r--src/listing-spec.js570
1 files changed, 75 insertions, 495 deletions
diff --git a/src/listing-spec.js b/src/listing-spec.js
index 36637ee0..4853f812 100644
--- a/src/listing-spec.js
+++ b/src/listing-spec.js
@@ -1,8 +1,9 @@
 import {OFFICIAL_GROUP_DIRECTORY} from './util/magic-constants.js';
 
 import {
-  empty,
   accumulateSum,
+  empty,
+  showAggregate,
 } from './util/sugar.js';
 
 import {
@@ -20,565 +21,111 @@ const listingSpec = [];
 listingSpec.push({
   directory: 'albums/by-name',
   stringsKey: 'listAlbums.byName',
+  contentFunction: 'listAlbumsByName',
 
   seeAlso: [
     'tracks/by-album',
   ],
-
-  data: ({wikiData: {albumData}}) =>
-    sortAlphabetically(albumData.slice()),
-
-  row: (album, {language, link}) =>
-    language.$('listingPage.listAlbums.byName.item', {
-      album: link.album(album),
-      tracks: language.countTracks(album.tracks.length, {unit: true}),
-    }),
 });
 
 listingSpec.push({
   directory: 'albums/by-tracks',
   stringsKey: 'listAlbums.byTracks',
-
-  data: ({wikiData: {albumData}}) =>
-    albumData.slice()
-      .sort((a, b) => b.tracks.length - a.tracks.length),
-
-  row: (album, {language, link}) =>
-    language.$('listingPage.listAlbums.byTracks.item', {
-      album: link.album(album),
-      tracks: language.countTracks(album.tracks.length, {unit: true}),
-    }),
+  contentFunction: 'listAlbumsByTracks',
 });
 
 listingSpec.push({
   directory: 'albums/by-duration',
   stringsKey: 'listAlbums.byDuration',
-
-  data: ({wikiData: {albumData}}) =>
-    albumData
-      .map(album => ({
-        album,
-        duration: getTotalDuration(album.tracks),
-      }))
-      .filter(({duration}) => duration > 0)
-      .sort((a, b) => b.duration - a.duration),
-
-  row: ({album, duration}, {language, link}) =>
-    language.$('listingPage.listAlbums.byDuration.item', {
-      album: link.album(album),
-      duration: language.formatDuration(duration),
-    }),
+  contentFunction: 'listAlbumsByDuration',
 });
 
 listingSpec.push({
   directory: 'albums/by-date',
   stringsKey: 'listAlbums.byDate',
+  contentFunction: 'listAlbumsByDate',
 
   seeAlso: [
     'tracks/by-date',
   ],
-
-  data: ({wikiData: {albumData}}) =>
-    sortChronologically(
-      albumData
-        .filter(album => album.date)),
-
-  row: (album, {language, link}) =>
-    language.$('listingPage.listAlbums.byDate.item', {
-      album: link.album(album),
-      date: language.formatDate(album.date),
-    }),
 });
 
 listingSpec.push({
   directory: 'albums/by-date-added',
   stringsKey: 'listAlbums.byDateAdded',
-
-  data: ({wikiData: {albumData}}) =>
-    chunkByProperties(
-      sortAlphabetically(albumData.filter(a => a.dateAddedToWiki))
-        .sort((a, b) => {
-          if (a.dateAddedToWiki < b.dateAddedToWiki) return -1;
-          if (a.dateAddedToWiki > b.dateAddedToWiki) return 1;
-        }),
-      ['dateAddedToWiki']),
-
-  html: (data, {html, language, link}) =>
-    html.tag('dl',
-      data.flatMap(({dateAddedToWiki, chunk: albums}) => [
-        html.tag('dt',
-          {class: ['content-heading']},
-          language.$('listingPage.listAlbums.byDateAdded.date', {
-            date: language.formatDate(dateAddedToWiki),
-          })),
-
-        html.tag('dd',
-          html.tag('ul',
-            albums.map((album) =>
-              html.tag('li',
-                language.$('listingPage.listAlbums.byDateAdded.album', {
-                  album: link.album(album),
-                }))))),
-      ])),
+  contentFunction: 'listAlbumsByDateAdded',
 });
 
 listingSpec.push({
   directory: 'artists/by-name',
   stringsKey: 'listArtists.byName',
-
-  data: ({wikiData: {artistData}}) =>
-    sortAlphabetically(artistData.slice())
-      .map(artist => ({
-        artist,
-        contributions: getArtistNumContributions(artist),
-      })),
-
-  row: ({artist, contributions}, {language, link}) =>
-    language.$('listingPage.listArtists.byName.item', {
-      artist: link.artist(artist),
-      contributions: language.countContributions(contributions, {
-        unit: true,
-      }),
-    }),
+  contentFunction: 'listArtistsByName',
 });
 
 listingSpec.push({
   directory: 'artists/by-contribs',
   stringsKey: 'listArtists.byContribs',
-
-  data: ({wikiData: {artistData, wikiInfo}}) => ({
-    toTracks: artistData
-      .map(artist => ({
-        artist,
-        contributions:
-          artist.tracksAsContributor.length +
-          artist.tracksAsArtist.length,
-      }))
-      .sort((a, b) => b.contributions - a.contributions)
-      .filter(({contributions}) => contributions),
-
-    toArtAndFlashes: artistData
-      .map(artist => ({
-        artist,
-        contributions:
-          artist.tracksAsCoverArtist.length +
-          artist.albumsAsCoverArtist.length +
-          artist.albumsAsWallpaperArtist.length +
-          artist.albumsAsBannerArtist.length +
-          (wikiInfo.enableFlashesAndGames
-            ? artist.flashesAsContributor.length
-            : 0),
-      }))
-      .sort((a, b) => b.contributions - a.contributions)
-      .filter(({contributions}) => contributions),
-
-    // This is a kinda naughty hack, 8ut like, it's the only place
-    // we'd 8e passing wikiData to html() otherwise, so like....
-    // (Ok we do do this again once later.)
-    showAsFlashes: wikiInfo.enableFlashesAndGames,
-  }),
-
-  html: (
-    {toTracks, toArtAndFlashes, showAsFlashes},
-    {html, language, link}
-  ) =>
-    html.tag('div', {class: 'content-columns'}, [
-      html.tag('div', {class: 'column'}, [
-        html.tag('h2',
-          language.$('listingPage.misc.trackContributors')),
-
-        html.tag('ul',
-          toTracks.map(({artist, contributions}) =>
-            html.tag('li',
-              language.$('listingPage.listArtists.byContribs.item', {
-                artist: link.artist(artist),
-                contributions: language.countContributions(contributions, {
-                  unit: true,
-                }),
-              })))),
-      ]),
-
-      html.tag('div', {class: 'column'}, [
-        html.tag('h2',
-          language.$(
-            'listingPage.misc' +
-              (showAsFlashes
-                ? '.artAndFlashContributors'
-                : '.artContributors'))),
-
-        html.tag('ul',
-          toArtAndFlashes.map(({artist, contributions}) =>
-            html.tag('li',
-              language.$('listingPage.listArtists.byContribs.item', {
-                artist: link.artist(artist),
-                contributions:
-                  language.countContributions(contributions, {unit: true}),
-              })))),
-      ]),
-  ]),
+  contentFunction: 'listArtistsByContributions',
 });
 
 listingSpec.push({
   directory: 'artists/by-commentary',
   stringsKey: 'listArtists.byCommentary',
-
-  data: ({wikiData: {artistData}}) =>
-    artistData
-      .map(artist => ({
-        artist,
-        entries:
-          artist.tracksAsCommentator.length +
-          artist.albumsAsCommentator.length,
-      }))
-      .filter(({entries}) => entries)
-      .sort((a, b) => b.entries - a.entries),
-
-  row: ({artist, entries}, {language, link}) =>
-    language.$('listingPage.listArtists.byCommentary.item', {
-      artist: link.artist(artist),
-      entries: language.countCommentaryEntries(entries, {unit: true}),
-    }),
+  contentFunction: 'listArtistsByCommentaryEntries',
 });
 
 listingSpec.push({
   directory: 'artists/by-duration',
   stringsKey: 'listArtists.byDuration',
-
-  data: ({wikiData: {artistData}}) =>
-    artistData
-      .map((artist) => ({
-        artist,
-        duration: getTotalDuration([
-          ...(artist.tracksAsArtist ?? []),
-          ...(artist.tracksAsContributor ?? []),
-        ], {originalReleasesOnly: true}),
-      }))
-      .filter(({duration}) => duration > 0)
-      .sort((a, b) => b.duration - a.duration),
-
-  row: ({artist, duration}, {language, link}) =>
-    language.$('listingPage.listArtists.byDuration.item', {
-      artist: link.artist(artist),
-      duration: language.formatDuration(duration),
-    }),
+  contentFunction: 'listArtistsByDuration',
 });
 
 listingSpec.push({
   directory: 'artists/by-latest',
   stringsKey: 'listArtists.byLatest',
-
-  data({wikiData: {
-    albumData,
-    flashData,
-    trackData,
-    wikiInfo,
-  }}) {
-    const processContribs = values => {
-      const filteredValues = values
-        .filter(value => value.date && !empty(value.contribs));
-
-      const datedArtistLists = sortByDate(filteredValues)
-        .map(({
-          contribs,
-          date,
-        }) => ({
-          artists: contribs.map(({who}) => who),
-          date,
-        }));
-
-      const remainingArtists = new Set(datedArtistLists.flatMap(({artists}) => artists));
-      const artistEntries = [];
-
-      for (let i = datedArtistLists.length - 1; i >= 0; i--) {
-        const {artists, date} = datedArtistLists[i];
-        for (const artist of artists) {
-          if (!remainingArtists.has(artist))
-            continue;
-
-          remainingArtists.delete(artist);
-          artistEntries.push({
-            artist,
-            date,
-
-            // For sortChronologically!
-            directory: artist.directory,
-            name: artist.name,
-          });
-        }
-
-        // Early exit: If we've gotten every artist, there's no need to keep
-        // going.
-        if (remainingArtists.size === 0)
-          break;
-      }
-
-      return sortChronologically(artistEntries, {latestFirst: true});
-    };
-
-    // Tracks are super easy to sort because they only have one pertinent
-    // date: the date the track was released on.
-
-    const toTracks = processContribs(
-      trackData.map(({
-        artistContribs,
-        date,
-      }) => ({
-        contribs: artistContribs,
-        date,
-      })));
-
-    // Artworks are a bit more involved because there are multiple dates
-    // involved - cover artists correspond to one date, wallpaper artists to
-    // another, etc.
-
-    const toArtAndFlashes = processContribs([
-      ...trackData.map(({
-        coverArtistContribs,
-        coverArtDate,
-      }) => ({
-        contribs: coverArtistContribs,
-        date: coverArtDate,
-      })),
-
-      ...flashData
-        ? flashData.map(({
-            contributorContribs,
-            date,
-          }) => ({
-            contribs: contributorContribs,
-            date,
-          }))
-        : [],
-
-      ...albumData.flatMap(({
-        bannerArtistContribs,
-        coverArtistContribs,
-        coverArtDate,
-        date,
-        wallpaperArtistContribs,
-      }) => [
-        {
-          contribs: coverArtistContribs,
-          date: coverArtDate,
-        },
-        {
-          contribs: bannerArtistContribs,
-          date, // TODO: bannerArtDate (see issue #90)
-        },
-        {
-          contribs: wallpaperArtistContribs,
-          date, // TODO: wallpaperArtDate (see issue #90)
-        },
-      ]),
-    ]);
-
-    return {
-      toArtAndFlashes,
-      toTracks,
-
-      // (Ok we did it again.)
-      // This is a kinda naughty hack, 8ut like, it's the only place
-      // we'd 8e passing wikiData to html() otherwise, so like....
-      showAsFlashes: wikiInfo.enableFlashesAndGames,
-    };
-  },
-
-  html: (
-    {toTracks, toArtAndFlashes, showAsFlashes},
-    {html, language, link}
-  ) =>
-    html.tag('div', {class: 'content-columns'}, [
-      html.tag('div', {class: 'column'}, [
-        html.tag('h2',
-          language.$('listingPage.misc.trackContributors')),
-
-        html.tag('ul',
-          toTracks.map(({artist, date}) =>
-            html.tag('li',
-              language.$('listingPage.listArtists.byLatest.item', {
-                artist: link.artist(artist),
-                date: language.formatDate(date),
-              })))),
-      ]),
-
-      html.tag('div', {class: 'column'}, [
-        html.tag('h2',
-          language.$(
-            'listingPage.misc' +
-              (showAsFlashes
-                ? '.artAndFlashContributors'
-                : '.artContributors'))),
-
-        html.tag('ul',
-          toArtAndFlashes.map(({artist, date}) =>
-            html.tag('li',
-              language.$('listingPage.listArtists.byLatest.item', {
-                artist: link.artist(artist),
-                date: language.formatDate(date),
-              })))),
-      ]),
-    ]),
+  contentFunction: 'listArtistsByLatestContribution',
 });
 
 listingSpec.push({
   directory: 'groups/by-name',
   stringsKey: 'listGroups.byName',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupData}}) =>
-    sortAlphabetically(groupData.slice()),
-
-  row: (group, {language, link}) =>
-    language.$('listingPage.listGroups.byCategory.group', {
-      group: link.groupInfo(group),
-      gallery: link.groupGallery(group, {
-        text: language.$('listingPage.listGroups.byCategory.group.gallery'),
-      }),
-    }),
+  contentFunction: 'listGroupsByName',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
   directory: 'groups/by-category',
   stringsKey: 'listGroups.byCategory',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupCategoryData}}) =>
-    groupCategoryData
-      .map(category => ({
-        category,
-        groups: category.groups,
-      })),
-
-  html: (data, {html, language, link}) =>
-    html.tag('dl',
-      data.flatMap(({category, groups}) => [
-        html.tag('dt',
-          {class: ['content-heading']},
-          language.$('listingPage.listGroups.byCategory.category', {
-            category: empty(groups)
-              ? category.name
-              : link.groupInfo(groups[0], {
-                  text: category.name,
-                }),
-          })),
-
-        html.tag('dd',
-          empty(groups)
-            ? null // todo: #85
-            : html.tag('ul',
-                category.groups.map(group =>
-                  html.tag('li',
-                    language.$('listingPage.listGroups.byCategory.group', {
-                      group: link.groupInfo(group),
-                      gallery: link.groupGallery(group, {
-                        text: language.$('listingPage.listGroups.byCategory.group.gallery'),
-                      }),
-                    }))))),
-      ])),
+  contentFunction: 'listGroupsByCategory',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
   directory: 'groups/by-albums',
   stringsKey: 'listGroups.byAlbums',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupData}}) =>
-    groupData
-      .map(group => ({
-        group,
-        albums: group.albums.length
-      }))
-      .sort((a, b) => b.albums - a.albums),
-
-  row: ({group, albums}, {language, link}) =>
-    language.$('listingPage.listGroups.byAlbums.item', {
-      group: link.groupInfo(group),
-      albums: language.countAlbums(albums, {unit: true}),
-    }),
+  contentFunction: 'listGroupsByAlbums',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
   directory: 'groups/by-tracks',
   stringsKey: 'listGroups.byTracks',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupData}}) =>
-    groupData
-      .map((group) => ({
-        group,
-        tracks: accumulateSum(
-          group.albums,
-          ({tracks}) => tracks.length),
-      }))
-      .sort((a, b) => b.tracks - a.tracks),
-
-  row: ({group, tracks}, {language, link}) =>
-    language.$('listingPage.listGroups.byTracks.item', {
-      group: link.groupInfo(group),
-      tracks: language.countTracks(tracks, {unit: true}),
-    }),
+  contentFunction: 'listGroupsByTracks',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
   directory: 'groups/by-duration',
   stringsKey: 'listGroups.byDuration',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupData}}) =>
-    groupData
-      .map(group => ({
-        group,
-        duration: getTotalDuration(
-          group.albums.flatMap(album => album.tracks),
-          {originalReleasesOnly: true}),
-      }))
-      .filter(({duration}) => duration > 0)
-      .sort((a, b) => b.duration - a.duration),
-
-  row: ({group, duration}, {language, link}) =>
-    language.$('listingPage.listGroups.byDuration.item', {
-      group: link.groupInfo(group),
-      duration: language.formatDuration(duration),
-    }),
+  contentFunction: 'listGroupsByDuration',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
   directory: 'groups/by-latest-album',
   stringsKey: 'listGroups.byLatest',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableGroupUI,
-
-  data: ({wikiData: {groupData}}) =>
-    sortChronologically(
-      groupData
-        .map(group => {
-          const albums = group.albums.filter(a => a.date);
-          return !empty(albums) && {
-            group,
-            directory: group.directory,
-            name: group.name,
-            date: albums[albums.length - 1].date,
-          };
-        })
-        .filter(Boolean),
-      {latestFirst: true}),
-
-  row: ({group, date}, {language, link}) =>
-    language.$('listingPage.listGroups.byLatest.item', {
-      group: link.groupInfo(group),
-      date: language.formatDate(date),
-    }),
+  contentFunction: 'listGroupsByLatestAlbum',
+  featureFlag: 'enableGroupUI',
 });
 
 listingSpec.push({
@@ -737,9 +284,7 @@ listingSpec.push({
 listingSpec.push({
   directory: 'tracks/in-flashes/by-album',
   stringsKey: 'listTracks.inFlashes.byAlbum',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableFlashesAndGames,
+  featureFlag: 'enableFlashesAndGames',
 
   data: ({wikiData: {trackData}}) =>
     chunkByProperties(
@@ -771,9 +316,7 @@ listingSpec.push({
 listingSpec.push({
   directory: 'tracks/in-flashes/by-flash',
   stringsKey: 'listTracks.inFlashes.byFlash',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableFlashesAndGames,
+  featureFlag: 'enableFlashesAndGames',
 
   data: ({wikiData: {flashData}}) =>
     sortFlashesChronologically(flashData.slice())
@@ -872,9 +415,7 @@ listingSpec.push(listTracksWithProperty('midiProjectFiles', {
 listingSpec.push({
   directory: 'tags/by-name',
   stringsKey: 'listTags.byName',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableArtTagUI,
+  featureFlag: 'enableArtTagUI',
 
   data: ({wikiData: {artTagData}}) =>
     sortAlphabetically(
@@ -899,9 +440,7 @@ listingSpec.push({
 listingSpec.push({
   directory: 'tags/by-uses',
   stringsKey: 'listTags.byUses',
-
-  condition: ({wikiData: {wikiInfo}}) =>
-    wikiInfo.enableArtTagUI,
+  featureFlag: 'enableArtTagUI',
 
   data: ({wikiData: {artTagData}}) =>
     artTagData
@@ -1086,34 +625,75 @@ listingSpec.push({
     ]),
 });
 
+{
+  const errors = [];
+
+  for (const listing of listingSpec) {
+    if (listing.seeAlso) {
+      const suberrors = [];
+
+      for (let i = 0; i < listing.seeAlso.length; i++) {
+        const directory = listing.seeAlso[i];
+        const match = listingSpec.find(listing => listing.directory === directory);
+
+        if (match) {
+          listing.seeAlso[i] = match;
+        } else {
+          listing.seeAlso[i] = null;
+          suberrors.push(new Error(`(index: ${i}) Didn't find a listing matching ${directory}`))
+        }
+      }
+
+      listing.seeAlso = listing.seeAlso.filter(Boolean);
+
+      if (!empty(suberrors)) {
+        errors.push(new AggregateError(suberrors, `Errors matching "see also" listings for ${listing.directory}`));
+      }
+    } else {
+      listing.seeAlso = null;
+    }
+  }
+
+  if (!empty(errors)) {
+    const aggregate = new AggregateError(errors, `Errors validating listings`);
+    showAggregate(aggregate, {showTraces: false});
+  }
+}
+
 const filterListings = (directoryPrefix) =>
   listingSpec.filter(l => l.directory.startsWith(directoryPrefix));
 
 const listingTargetSpec = [
   {
-    title: ({language}) => language.$('listingPage.target.album'),
+    stringsKey: 'album',
     listings: filterListings('album'),
   },
   {
-    title: ({language}) => language.$('listingPage.target.artist'),
+    stringsKey: 'artist',
     listings: filterListings('artist'),
   },
   {
-    title: ({language}) => language.$('listingPage.target.group'),
+    stringsKey: 'group',
     listings: filterListings('group'),
   },
   {
-    title: ({language}) => language.$('listingPage.target.track'),
+    stringsKey: 'track',
     listings: filterListings('track'),
   },
   {
-    title: ({language}) => language.$('listingPage.target.tag'),
+    stringsKey: 'tag',
     listings: filterListings('tag'),
   },
   {
-    title: ({language}) => language.$('listingPage.target.other'),
+    stringsKey: 'other',
     listings: listingSpec.filter(l => l.groupUnderOther),
   },
 ];
 
+for (const target of listingTargetSpec) {
+  for (const listing of target.listings) {
+    listing.target = target;
+  }
+}
+
 export {listingSpec, listingTargetSpec};