« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateTrackInfoPage.js4
-rw-r--r--src/content/dependencies/generateTrackInfoPageContent.js188
-rw-r--r--src/content/dependencies/generateTrackListDividedByGroups.js87
-rw-r--r--src/content/util/groupTracksByGroup.js23
-rw-r--r--src/page/track.js6
-rw-r--r--src/strings-default.json4
-rw-r--r--src/write/build-modes/live-dev-server.js2
7 files changed, 262 insertions, 52 deletions
diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js
index 5900b27e..61b2b165 100644
--- a/src/content/dependencies/generateTrackInfoPage.js
+++ b/src/content/dependencies/generateTrackInfoPage.js
@@ -16,7 +16,7 @@ export default {
 
   extraDependencies: ['language'],
 
-  relations(relation, track) {
+  relations(relation, track, {topLevelGroups}) {
     return {
       layout: relation('generatePageLayout'),
 
@@ -57,7 +57,7 @@ export default {
       albumNavLinks: relation('generateAlbumNavLinks', track.album, track),
       chronologyLinks: relation('generateChronologyLinks'),
 
-      content: relation('generateTrackInfoPageContent', track),
+      content: relation('generateTrackInfoPageContent', track, {topLevelGroups}),
       sidebar: relation('generateAlbumSidebar', track.album, track),
       albumStyleRules: relation('generateAlbumStyleRules', track.album),
       colorStyleRules: relation('generateColorStyleRules', track.color),
diff --git a/src/content/dependencies/generateTrackInfoPageContent.js b/src/content/dependencies/generateTrackInfoPageContent.js
index 3f37a0c9..eea387bb 100644
--- a/src/content/dependencies/generateTrackInfoPageContent.js
+++ b/src/content/dependencies/generateTrackInfoPageContent.js
@@ -4,6 +4,7 @@ export default {
   contentDependencies: [
     'generateContentHeading',
     'generateCoverArtwork',
+    'generateTrackListDividedByGroups',
     'linkAlbum',
     'linkContribution',
     'linkExternal',
@@ -16,52 +17,104 @@ export default {
     'transformMultiline',
   ],
 
-  relations(relation, track) {
-    const relations = {};
-
+  relations(relation, track, {topLevelGroups}) {
     const {album} = track;
 
+    const relations = {};
+    const sections = relations.sections = {};
+
     const contributionLinksRelation = contribs =>
       contribs.map(contrib =>
         relation('linkContribution', contrib.who, contrib.what));
 
+    // Section: Release info
+
+    const releaseInfo = sections.releaseInfo = {};
+
+    releaseInfo.artistContributionLinks =
+      contributionLinksRelation(track.artistContribs);
+
     if (track.hasUniqueCoverArt) {
       relations.cover =
         relation('generateCoverArtwork', track.artTags);
-      relations.coverArtistLinks =
+      releaseInfo.coverArtistContributionLinks =
         contributionLinksRelation(track.coverArtistContribs);
     } else if (album.hasCoverArt) {
       relations.cover =
         relation('generateCoverArtwork', album.artTags);
-      relations.coverArtistLinks = null;
     } else {
       relations.cover = null;
-      relations.coverArtistLinks = null;
     }
 
-    relations.artistLinks =
-      contributionLinksRelation(track.artistContribs);
+    // Section: Listen on
 
-    relations.externalLinks =
-      track.urls.map(url =>
-        relation('linkExternal', url));
+    const listen = sections.listen = {};
 
-    relations.otherReleasesHeading =
+    listen.heading =
       relation('generateContentHeading');
 
-    relations.otherReleases =
-      track.otherReleases.map(track => ({
-        trackLink: relation('linkTrack', track),
-        albumLink: relation('linkAlbum', track.album),
-      }));
+    if (!empty(track.urls)) {
+      listen.externalLinks =
+        track.urls.map(url =>
+          relation('linkExternal', url));
+    }
+
+    // Section: Other releases
+
+    if (!empty(track.otherReleases)) {
+      const otherReleases = sections.otherReleases = {};
+
+      otherReleases.heading =
+        relation('generateContentHeading');
+
+      otherReleases.items =
+        track.otherReleases.map(track => ({
+          trackLink: relation('linkTrack', track),
+          albumLink: relation('linkAlbum', track.album),
+        }));
+    }
+
+    // Section: Contributors
 
     if (!empty(track.contributorContribs)) {
-      relations.contributorsHeading =
+      const contributors = sections.contributors = {};
+
+      contributors.heading =
         relation('generateContentHeading');
-      relations.contributorLinks =
+
+      contributors.contributionLinks =
         contributionLinksRelation(track.contributorContribs);
     }
 
+    // Section: Referenced tracks
+
+    if (!empty(track.referencedTracks)) {
+      const references = sections.references = {};
+
+      references.heading =
+        relation('generateContentHeading');
+
+      references.items =
+        track.referencedTracks.map(track => ({
+          trackLink: relation('linkTrack', track),
+          contributionLinks: contributionLinksRelation(track.artistContribs),
+        }));
+    }
+
+    // Section: Tracks that reference
+
+    if (!empty(track.referencedByTracks)) {
+      const referencedBy = sections.referencedBy = {};
+
+      referencedBy.heading =
+        relation('generateContentHeading');
+
+      referencedBy.list =
+        relation('generateTrackListDividedByGroups',
+          track.referencedByTracks,
+          topLevelGroups);
+    }
+
     return relations;
   },
 
@@ -70,6 +123,7 @@ export default {
 
     const {album} = track;
 
+    data.name = track.name;
     data.date = track.date;
     data.duration = track.duration;
 
@@ -99,14 +153,28 @@ export default {
   }) {
     const content = {};
 
-    const formatContributions = contributionLinks =>
-      language.formatConjunctionList(
-        contributionLinks.map(link =>
-          link
-            .slots({
-              showContribution: true,
-              showIcons: true,
-            })));
+    const {sections: sec} = relations;
+
+    const formatContributions =
+      (contributionLinks, {showContribution = true, showIcons = true} = {}) =>
+        language.formatConjunctionList(
+          contributionLinks.map(link =>
+            link.slots({showContribution, showIcons})));
+
+    const formatTrackItem = ({trackLink, contributionLinks}) =>
+      html.tag('li',
+        language.$('trackList.item.withArtists', {
+          track: trackLink,
+          by:
+            html.tag('span', {class: 'by'},
+              language.$('trackList.item.withArtists.by', {
+                artists:
+                  formatContributions(contributionLinks, {
+                    showContribution: false,
+                    showIcons: false,
+                  }),
+              })),
+        }));
 
     if (data.hasUniqueCoverArt) {
       content.cover = relations.cover
@@ -139,14 +207,14 @@ export default {
           [html.onlyIfContent]: true,
           [html.joinChildren]: html.tag('br'),
         }, [
-          !empty(relations.artistLinks) &&
+          sec.releaseInfo.artistContributionLinks &&
             language.$('releaseInfo.by', {
-              artists: formatContributions(relations.artistLinks),
+              artists: formatContributions(sec.releaseInfo.artistContributionLinks),
             }),
 
-          !empty(relations.coverArtistLinks) &&
+          sec.releaseInfo.coverArtistContributionLinks &&
             language.$('releaseInfo.coverArtBy', {
-              artists: formatContributions(relations.coverArtistLinks),
+              artists: formatContributions(sec.releaseInfo.coverArtistContributionLinks),
             }),
 
           data.date &&
@@ -191,22 +259,25 @@ export default {
           ]),
         */
 
-        html.tag('p',
-          (empty(relations.externalLinks)
-            ? language.$('releaseInfo.listenOn.noLinks')
-            : language.$('releaseInfo.listenOn', {
-                links: language.formatDisjunctionList(relations.externalLinks),
-              }))),
-
-        !empty(relations.otherReleases) && [
-          relations.otherReleasesHeading
+        sec.listen.heading.slots({
+          id: 'listen-on',
+          title:
+            (sec.listen.externalLinks
+              ? language.$('releaseInfo.listenOn', {
+                  links: language.formatDisjunctionList(sec.listen.externalLinks),
+                })
+              : language.$('releaseInfo.listenOn.noLinks')),
+        }),
+
+        sec.otherReleases && [
+          sec.otherReleases.heading
             .slots({
               id: 'also-released-as',
               title: language.$('releaseInfo.alsoReleasedAs'),
             }),
 
           html.tag('ul',
-            relations.otherReleases.map(({trackLink, albumLink}) =>
+            sec.otherReleases.items.map(({trackLink, albumLink}) =>
               html.tag('li',
                 language.$('releaseInfo.alsoReleasedAs.item', {
                   track: trackLink,
@@ -214,21 +285,48 @@ export default {
                 })))),
         ],
 
-        relations.contributorLinks && [
-          relations.contributorsHeading
+        sec.contributors && [
+          sec.contributors.heading
             .slots({
               id: 'contributors',
               title: language.$('releaseInfo.contributors'),
             }),
 
-          html.tag('ul', relations.contributorLinks.map(contributorLink =>
+          html.tag('ul', sec.contributors.contributionLinks.map(contributionLink =>
             html.tag('li',
-              contributorLink
+              contributionLink
                 .slots({
                   showIcons: true,
                   showContribution: true,
                 })))),
         ],
+
+        sec.references && [
+          sec.references.heading
+            .slots({
+              id: 'references',
+              title:
+                language.$('releaseInfo.tracksReferenced', {
+                  track: html.tag('i', data.name),
+                }),
+            }),
+
+          html.tag('ul',
+            sec.references.items.map(formatTrackItem)),
+        ],
+
+        sec.referencedBy && [
+          sec.referencedBy.heading
+            .slots({
+              id: 'referenced-by',
+              title:
+                language.$('releaseInfo.tracksThatReference', {
+                  track: html.tag('i', data.name),
+                }),
+            }),
+
+          sec.referencedBy.list,
+        ],
       ]),
     };
 
diff --git a/src/content/dependencies/generateTrackListDividedByGroups.js b/src/content/dependencies/generateTrackListDividedByGroups.js
new file mode 100644
index 00000000..69fedb28
--- /dev/null
+++ b/src/content/dependencies/generateTrackListDividedByGroups.js
@@ -0,0 +1,87 @@
+import {empty} from '../../util/sugar.js';
+
+import groupTracksByGroup from '../util/groupTracksByGroup.js';
+
+export default {
+  contentDependencies: ['linkTrack', 'linkContribution'],
+
+  extraDependencies: ['html', 'language'],
+
+  relations(relation, tracks, groups) {
+    if (empty(tracks)) {
+      return {};
+    }
+
+    const trackRelations = track => ({
+      trackLink:
+        relation('linkTrack', track),
+
+      contributionLinks:
+        track.artistContribs.map(contrib =>
+          relation('linkContribution', contrib.who, contrib.what)),
+    });
+
+    if (empty(groups)) {
+      return {
+        flatItems: tracks.map(trackRelations),
+      };
+    }
+
+    const lists = groupTracksByGroup(tracks, groups);
+
+    return {
+      groupedItems:
+        Array.from(lists.entries()).map(([groupOrOther, tracks]) => ({
+          ...(groupOrOther === 'other'
+                ? {other: true}
+                : {groupLink: relation('linkGroup', groupOrOther)}),
+
+          items: tracks.map(trackRelations),
+        })),
+    };
+  },
+
+  generate(relations, {html, language}) {
+    // TODO: This is copy-pasted from generateTrackInfoPageContent, seems bad
+
+    const formatContributions =
+      (contributionLinks, {showContribution = true, showIcons = true} = {}) =>
+        language.formatConjunctionList(
+          contributionLinks.map(link =>
+            link.slots({showContribution, showIcons})));
+
+    const formatTrackItem = ({trackLink, contributionLinks}) =>
+      html.tag('li',
+        language.$('trackList.item.withArtists', {
+          track: trackLink,
+          by:
+            html.tag('span', {class: 'by'},
+              language.$('trackList.item.withArtists.by', {
+                artists:
+                  formatContributions(contributionLinks, {
+                    showContribution: false,
+                    showIcons: false,
+                  }),
+              })),
+        }));
+
+    if (relations.flatItems) {
+      return html.tag('ul',
+        relations.flatItems.map(formatTrackItem));
+    }
+
+    return html.tag('dl',
+      relations.groupedItems.map(({other, groupLink, items}) => [
+        html.tag('dt',
+          (other
+            ? language.$('trackList.group.fromOther')
+            : language.$('trackList.group', {
+                group: groupLink
+              }))),
+
+        html.tag('dd',
+          html.tag('ul',
+            items.map(formatTrackItem))),
+      ]));
+  },
+};
diff --git a/src/content/util/groupTracksByGroup.js b/src/content/util/groupTracksByGroup.js
new file mode 100644
index 00000000..bae2c8c5
--- /dev/null
+++ b/src/content/util/groupTracksByGroup.js
@@ -0,0 +1,23 @@
+import {empty} from '../../util/sugar.js';
+
+export default function groupTracksByGroup(tracks, groups) {
+  const lists = new Map(groups.map(group => [group, []]));
+  lists.set('other', []);
+
+  for (const track of tracks) {
+    const group = groups.find(group => group.albums.includes(track.album));
+    if (group) {
+      lists.get(group).push(track);
+    } else {
+      other.get('other').push(track);
+    }
+  }
+
+  for (const [key, tracks] of lists.entries()) {
+    if (empty(tracks)) {
+      lists.delete(key);
+    }
+  }
+
+  return lists;
+}
diff --git a/src/page/track.js b/src/page/track.js
index e75b6958..9b3867c9 100644
--- a/src/page/track.js
+++ b/src/page/track.js
@@ -6,7 +6,7 @@ export function targets({wikiData}) {
   return wikiData.trackData;
 }
 
-export function pathsForTarget(track) {
+export function pathsForTarget(track, {wikiInfo}) {
   return [
     {
       type: 'page',
@@ -14,7 +14,9 @@ export function pathsForTarget(track) {
 
       contentFunction: {
         name: 'generateTrackInfoPage',
-        args: [track],
+        args: [track, {
+          topLevelGroups: wikiInfo.divideTrackListsByGroups,
+        }],
       },
     },
   ];
diff --git a/src/strings-default.json b/src/strings-default.json
index a075f445..b7151f16 100644
--- a/src/strings-default.json
+++ b/src/strings-default.json
@@ -135,8 +135,8 @@
   "releaseInfo.midiProjectFiles.heading": "Download MIDI/project files:",
   "releaseInfo.note": "Note:",
   "trackList.section.withDuration": "{SECTION} ({DURATION}):",
-  "trackList.group": "{GROUP}:",
-  "trackList.group.other": "Other",
+  "trackList.group": "From {GROUP}:",
+  "trackList.group.fromOther": "From somewhere else:",
   "trackList.item.withDuration": "({DURATION}) {TRACK}",
   "trackList.item.withDuration.withArtists": "({DURATION}) {TRACK} {BY}",
   "trackList.item.withArtists": "{TRACK} {BY}",
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js
index 50caafe9..c15fc465 100644
--- a/src/write/build-modes/live-dev-server.js
+++ b/src/write/build-modes/live-dev-server.js
@@ -102,7 +102,7 @@ export async function go({
     }) => () =>
       targetless
         ? [pageSpec.writeTargetless({wikiData})]
-        : pageSpec.pathsForTarget(target))).flat();
+        : pageSpec.pathsForTarget(target, {wikiInfo: wikiData.wikiInfo}))).flat();
 
   logInfo`Will be serving a total of ${pages.length} pages.`;