« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/content/dependencies/generateDividedFeaturedInFlashesList.js142
-rw-r--r--src/content/dependencies/generateDividedTrackList.js4
-rw-r--r--src/content/dependencies/generateTrackFeaturedInFlashesList.js (renamed from src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js)32
-rw-r--r--src/content/dependencies/generateTrackInfoPage.js2
-rw-r--r--src/content/dependencies/listTracksInFlashesByAlbum.js2
-rw-r--r--src/data/things/Track.js39
-rw-r--r--src/data/things/WikiInfo.js30
-rw-r--r--src/strings-default.yaml11
8 files changed, 223 insertions, 39 deletions
diff --git a/src/content/dependencies/generateDividedFeaturedInFlashesList.js b/src/content/dependencies/generateDividedFeaturedInFlashesList.js
new file mode 100644
index 00000000..93e29991
--- /dev/null
+++ b/src/content/dependencies/generateDividedFeaturedInFlashesList.js
@@ -0,0 +1,142 @@
+import {empty, filterMultipleArrays, stitchArrays} from '#sugar';
+
+export default {
+  sprawl: ({flashSideData, wikiInfo}) => ({
+    enableFlashesAndGames:
+      wikiInfo.enableFlashesAndGames,
+
+    divideFlashListsBySides:
+      wikiInfo.divideFlashListsBySides,
+
+    allSides:
+      flashSideData,
+  }),
+
+  query(sprawl, features, _contextTrack) {
+    if (!sprawl.enableFlashesAndGames) {
+      return {dividingSides: [], dividedFeatures: []};
+    }
+
+    const {allSides} = sprawl;
+
+    const divisions = new Map();
+    const dividingSideIndices = [];
+    for (const {side, label} of sprawl.divideFlashListsBySides) {
+      divisions.set(side, {label, features: []});
+      dividingSideIndices.push(allSides.indexOf(side));
+    }
+
+    for (const feature of features) {
+      const sideIndex =
+        allSides.indexOf(feature.flash.side);
+
+      const closestDividingSideIndex =
+        dividingSideIndices.findLast(i => i <= sideIndex);
+
+      const closestDividingSide =
+        allSides.at(closestDividingSideIndex);
+
+      if (closestDividingSide) {
+        divisions.get(closestDividingSide).features.push(feature);
+      }
+    }
+
+    const dividingSides = Array.from(divisions.keys());
+    const dividingLabels = Array.from(divisions.values()).map(({label}) => label);
+    const dividedFeatures = Array.from(divisions.values()).map(({features}) => features);
+
+    filterMultipleArrays(
+      dividingSides,
+      dividingLabels,
+      dividedFeatures,
+      (_side, _label, dividedFeatures) => !empty(dividedFeatures));
+
+    return {dividingSides, dividingLabels, dividedFeatures};
+  },
+
+  relations: (relation, query, sprawl, features, contextTrack) => ({
+    flatList:
+      (empty(sprawl.divideFlashListsBySides)
+        ? relation('generateTrackFeaturedInFlashesList', features, contextTrack)
+        : null),
+
+    contentHeading:
+      relation('generateContentHeading'),
+
+    dividingSideLinks:
+      query.dividingSides
+        .map(side => relation('linkFlashSide', side)),
+
+    dividedFlashLists:
+      query.dividedFeatures
+        .map(features => relation('generateTrackFeaturedInFlashesList', features, contextTrack)),
+  }),
+
+  data: (query, _sprawl, _tracks) => ({
+    dividingSideNames:
+      query.dividingSides
+        .map(side => side.name),
+
+    dividingLabels:
+      query.dividingLabels,
+  }),
+
+  slots: {
+    headingString: {
+      type: 'string',
+    },
+  },
+
+  generate(data, relations, slots, {html, language}) {
+    if (relations.flatList) {
+      return relations.flatList;
+    }
+
+    stitchArrays({
+      sideLink: relations.dividingSideLinks,
+      label: data.dividingLabels,
+    }).forEach(({sideLink, label}) => {
+        sideLink.setSlot('color', false);
+
+        if (label) {
+          sideLink.setSlot('content', language.sanitize(label));
+        }
+      });
+
+    return (
+      html.tag('dl', {class: 'division-list'},
+        {[html.onlyIfContent]: true},
+
+        language.encapsulate('flashList', listCapsule =>
+          stitchArrays({
+            sideLink: relations.dividingSideLinks,
+            flashList: relations.dividedFlashLists,
+            sideName: data.dividingSideNames,
+            label: data.dividingLabels,
+          }).map(({sideLink, flashList, sideName, label}) => [
+              language.encapsulate(listCapsule, 'underSide', capsule =>
+                (slots.headingString
+                  ? relations.contentHeading.clone().slots({
+                      tag: 'dt',
+
+                      title:
+                        language.$(capsule, {side: sideLink}),
+
+                      stickyTitle:
+                        language.$(slots.headingString, 'sticky', 'fromGroup', {
+                          side:
+                            (label
+                              ? language.sanitize(label)
+                              : language.sanitize(sideName)),
+                        }),
+                    })
+
+                  : html.tag('dt',
+                      language.$(capsule, {
+                        side: sideLink,
+                      })))),
+
+              html.tag('dd', flashList),
+            ]))));
+  },
+};
diff --git a/src/content/dependencies/generateDividedTrackList.js b/src/content/dependencies/generateDividedTrackList.js
index f255fbaf..ea349ec8 100644
--- a/src/content/dependencies/generateDividedTrackList.js
+++ b/src/content/dependencies/generateDividedTrackList.js
@@ -100,7 +100,7 @@ export default {
 
                     title:
                       language.$(capsule, {
-                        group: groupLink
+                        group: groupLink.slot('color', false),
                       }),
 
                     stickyTitle:
@@ -110,7 +110,7 @@ export default {
                   })
                 : html.tag('dt',
                     language.$(capsule, {
-                      group: groupLink
+                      group: groupLink.slot('color', false),
                     })))),
 
             html.tag('dd', trackList),
diff --git a/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js b/src/content/dependencies/generateTrackFeaturedInFlashesList.js
index cd7bb014..f13368d1 100644
--- a/src/content/dependencies/generateTrackInfoPageFeaturedByFlashesList.js
+++ b/src/content/dependencies/generateTrackFeaturedInFlashesList.js
@@ -1,36 +1,14 @@
-import {sortFlashesChronologically} from '#sort';
 import {stitchArrays} from '#sugar';
 
 export default {
-  sprawl: ({wikiInfo}) => ({
-    enableFlashesAndGames:
-      wikiInfo.enableFlashesAndGames,
-  }),
-
-  query: (sprawl, track) => ({
-    sortedFeatures:
-      (sprawl.enableFlashesAndGames
-        ? sortFlashesChronologically(
-            track.allReleases.flatMap(track =>
-              track.featuredInFlashes.map(flash => ({
-                flash,
-                track,
-
-                // These properties are only used for the sort.
-                act: flash.act,
-                date: flash.date,
-              }))))
-        : []),
-  }),
-
-  relations: (relation, query, _sprawl, track) => ({
+  relations: (relation, features, track) => ({
     flashLinks:
-      query.sortedFeatures
+      features
         .map(({flash}) => relation('linkFlash', flash)),
 
     trackLinks:
-      query.sortedFeatures
-        .map(({track: directlyFeaturedTrack}) =>
+      features
+        .map(({as: directlyFeaturedTrack}) =>
           (directlyFeaturedTrack === track
             ? null
          : directlyFeaturedTrack.name === track.name
@@ -47,7 +25,7 @@ export default {
         trackLink: relations.trackLinks,
       }).map(({flashLink, trackLink}) => {
           const attributes = html.attributes();
-          const parts = ['releaseInfo.flashesThatFeature.item'];
+          const parts = ['flashList.item'];
           const options = {flash: flashLink};
 
           if (trackLink) {
diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js
index 9ff47ec3..5dce680b 100644
--- a/src/content/dependencies/generateTrackInfoPage.js
+++ b/src/content/dependencies/generateTrackInfoPage.js
@@ -108,7 +108,7 @@ export default {
         track),
 
     flashesThatFeatureList:
-      relation('generateTrackInfoPageFeaturedByFlashesList', track),
+      relation('generateDividedFeaturedInFlashesList', track.featuredInFlashes, track),
 
     lyricsSection:
       relation('generateLyricsSection', track.lyrics),
diff --git a/src/content/dependencies/listTracksInFlashesByAlbum.js b/src/content/dependencies/listTracksInFlashesByAlbum.js
index db5472db..d1d659af 100644
--- a/src/content/dependencies/listTracksInFlashesByAlbum.js
+++ b/src/content/dependencies/listTracksInFlashesByAlbum.js
@@ -16,7 +16,7 @@ export default {
     const flashes =
       tracks.map(tracks =>
         tracks.map(track =>
-          track.featuredInFlashes));
+          track.ownFeaturedInFlashes));
 
     // Filter out tracks that aren't featured in any flashes.
     // This listing doesn't perform any sorting within albums.
diff --git a/src/data/things/Track.js b/src/data/things/Track.js
index 5cb6388a..36e3733d 100644
--- a/src/data/things/Track.js
+++ b/src/data/things/Track.js
@@ -5,7 +5,7 @@ import {colors} from '#cli';
 import {input, V} from '#composite';
 import find, {keyRefRegex} from '#find';
 import {onlyItem} from '#sugar';
-import {sortByDate} from '#sort';
+import {sortByDate, sortFlashesChronologically} from '#sort';
 import Thing from '#thing';
 import {compareKebabCase} from '#wiki-data';
 
@@ -884,9 +884,44 @@ export class Track extends Thing {
       reverse: soupyReverse.input('tracksWhichSample'),
     }),
 
-    featuredInFlashes: reverseReferenceList({
+    ownFeaturedInFlashes: reverseReferenceList({
       reverse: soupyReverse.input('flashesWhichFeature'),
     }),
+
+    featuredInFlashes: [
+      {
+        dependencies: ['allReleases'],
+        compute: (continuation, {allReleases}) => continuation({
+          ['#data']:
+            allReleases.flatMap(track =>
+              track.ownFeaturedInFlashes.map(flash => ({
+                flash,
+                track,
+
+                // These properties are used for the upcoming sort.
+                act: flash.act,
+                date: flash.date,
+              }))),
+        }),
+      },
+
+      {
+        dependencies: ['#data'],
+        compute: (continuation, {'#data': data}) => continuation({
+          ['#sortedData']:
+            sortFlashesChronologically(data),
+        }),
+      },
+
+      {
+        dependencies: ['#sortedData'],
+        compute: ({'#sortedData': sortedData}) =>
+          sortedData.map(item => ({
+            flash: item.flash,
+            as: item.track,
+          })),
+      },
+    ],
   });
 
   static [Thing.yamlDocumentSpec] = {
diff --git a/src/data/things/WikiInfo.js b/src/data/things/WikiInfo.js
index ffb18cd8..364ae517 100644
--- a/src/data/things/WikiInfo.js
+++ b/src/data/things/WikiInfo.js
@@ -1,6 +1,5 @@
 import {input, V} from '#composite';
 import Thing from '#thing';
-import {parseContributionPresets, parseWallpaperParts} from '#yaml';
 
 import {
   isBoolean,
@@ -10,9 +9,16 @@ import {
   isNumber,
 } from '#validators';
 
+import {
+  parseAnnotatedReferences,
+  parseContributionPresets,
+  parseWallpaperParts,
+} from '#yaml';
+
 import {exitWithoutDependency, exposeConstant} from '#composite/control-flow';
 
 import {
+  annotatedReferenceList,
   canonicalBase,
   color,
   contentString,
@@ -30,7 +36,7 @@ export class WikiInfo extends Thing {
   static [Thing.wikiData] = 'wikiInfo';
   static [Thing.oneInstancePerWiki] = true;
 
-  static [Thing.getPropertyDescriptors] = ({Group}) => ({
+  static [Thing.getPropertyDescriptors] = ({FlashSide, Group}) => ({
     // Update & expose
 
     name: name(V('Unnamed Wiki')),
@@ -75,6 +81,15 @@ export class WikiInfo extends Thing {
       find: soupyFind.input('group'),
     }),
 
+    divideFlashListsBySides: annotatedReferenceList({
+      class: input.value(FlashSide),
+      find: soupyFind.input('flashSide'),
+
+      reference: input.value('side'),
+      annotation: input.value('label'),
+      thing: input.value('side'),
+    }),
+
     contributionPresets: {
       flags: {update: true, expose: true},
       update: {validate: isContributionPresetList},
@@ -147,6 +162,17 @@ export class WikiInfo extends Thing {
 
       'Divide Track Lists By Groups': {property: 'divideTrackListsByGroups'},
 
+      'Divide Flash Lists By Sides': {
+        property: 'divideFlashListsBySides',
+        transform: value =>
+          parseAnnotatedReferences(value, {
+            referenceField: 'Side',
+            referenceProperty: 'side',
+            annotationField: 'Label',
+            annotationProperty: 'label',
+          }),
+      },
+
       'Contribution Presets': {
         property: 'contributionPresets',
         transform: parseContributionPresets,
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 0b0df861..34d3cb07 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -339,10 +339,6 @@ releaseInfo:
     _: "Flashes that feature {TRACK}:"
     sticky: "Flashes that feature this track:"
 
-    item:
-      _: "{FLASH}"
-      asDifferentRelease: "{FLASH} (as {TRACK})"
-
   referencesArtworks: "References {ARTWORKS}."
   referencedByArtworks: "Referenced by {ARTWORKS}."
 
@@ -472,6 +468,13 @@ trackList:
     rerelease: >-
       {TRACK} (rerelease)
 
+flashList:
+  underSide: "under {SIDE}:"
+
+  item:
+    _: "{FLASH}"
+    asDifferentRelease: "{FLASH} (as {TRACK})"
+
 #
 # misc:
 #