« get me outta code hell

content: generateAlbumGalleryPage: artwork set switcher - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2025-04-12 17:54:24 -0300
committer(quasar) nebula <qznebula@protonmail.com>2025-04-12 17:54:24 -0300
commit27f4be16d385aab19f9b9f85a0a1a4822aecde38 (patch)
treef34e3f629361d1d5ab8b80030ef3b339a3e22fca
parent7da810a8509a3bbd5d3ab891230e1e7b8db510aa (diff)
content: generateAlbumGalleryPage: artwork set switcher
-rw-r--r--src/content/dependencies/generateAlbumGalleryAlbumGrid.js90
-rw-r--r--src/content/dependencies/generateAlbumGalleryPage.js242
-rw-r--r--src/content/dependencies/generateAlbumGalleryTrackGrid.js122
-rw-r--r--src/static/css/site.css4
-rw-r--r--src/strings-default.yaml9
5 files changed, 316 insertions, 151 deletions
diff --git a/src/content/dependencies/generateAlbumGalleryAlbumGrid.js b/src/content/dependencies/generateAlbumGalleryAlbumGrid.js
new file mode 100644
index 00000000..7f152871
--- /dev/null
+++ b/src/content/dependencies/generateAlbumGalleryAlbumGrid.js
@@ -0,0 +1,90 @@
+import {stitchArrays} from '#sugar';
+
+export default {
+  contentDependencies: [
+    'generateCoverGrid',
+    'image',
+    'linkAlbum',
+  ],
+
+  extraDependencies: ['html', 'language'],
+
+  query: (album) => ({
+    artworks:
+      (album.hasCoverArt
+        ? album.coverArtworks
+        : []),
+  }),
+
+  relations: (relation, query, album) => ({
+    coverGrid:
+      relation('generateCoverGrid'),
+
+    albumLinks:
+      query.artworks.map(_artwork =>
+        relation('linkAlbum', album)),
+
+    images:
+      query.artworks
+        .map(artwork => relation('image', artwork)),
+  }),
+
+  data: (query, album) => ({
+    albumName:
+      album.name,
+
+    artworkLabels:
+      query.artworks
+        .map(artwork => artwork.label),
+
+    artworkArtists:
+      query.artworks
+        .map(artwork => artwork.artistContribs
+          .map(contrib => contrib.artist.name)),
+  }),
+
+  slots: {
+    attributes: {type: 'attributes', mutable: false},
+  },
+
+  generate: (data, relations, slots, {html, language}) =>
+    html.tag('div',
+      {[html.onlyIfContent]: true},
+
+      slots.attributes,
+
+      [
+        relations.coverArtistsLine,
+
+        relations.coverGrid.slots({
+          links:
+            relations.albumLinks,
+
+          names:
+            data.artworkLabels
+              .map(label => label ?? data.albumName),
+
+          images:
+            stitchArrays({
+              image: relations.images,
+              label: data.artworkLabels,
+            }).map(({image, label}) =>
+                image.slots({
+                  missingSourceContent:
+                    language.$('misc.albumGalleryGrid.noCoverArt', {
+                      name:
+                        label ?? data.albumName,
+                    }),
+                })),
+
+          info:
+            data.artworkArtists.map(artists =>
+              language.$('misc.coverGrid.details.coverArtists', {
+                [language.onlyIfOptions]: ['artists'],
+
+                artists:
+                  language.formatUnitList(artists),
+              })),
+        }),
+      ]),
+};
diff --git a/src/content/dependencies/generateAlbumGalleryPage.js b/src/content/dependencies/generateAlbumGalleryPage.js
index b48d92af..8e6f4edf 100644
--- a/src/content/dependencies/generateAlbumGalleryPage.js
+++ b/src/content/dependencies/generateAlbumGalleryPage.js
@@ -1,18 +1,18 @@
-import {compareArrays, stitchArrays} from '#sugar';
+import {stitchArrays, unique} from '#sugar';
+import {getKebabCase} from '#wiki-data';
 
 export default {
   contentDependencies: [
-    'generateAlbumGalleryCoverArtistsLine',
+    'generateAlbumGalleryAlbumGrid',
     'generateAlbumGalleryNoTrackArtworksLine',
     'generateAlbumGalleryStatsLine',
+    'generateAlbumGalleryTrackGrid',
     'generateAlbumNavAccent',
     'generateAlbumSecondaryNav',
     'generateAlbumStyleRules',
-    'generateCoverGrid',
+    'generateIntrapageDotSwitcher',
     'generatePageLayout',
-    'image',
     'linkAlbum',
-    'linkTrack',
   ],
 
   extraDependencies: ['html', 'language'],
@@ -20,147 +20,82 @@ export default {
   query(album) {
     const query = {};
 
-    const tracksWithUniqueCoverArt =
+    const trackArtworkLabels =
       album.tracks
-        .filter(track => track.hasUniqueCoverArt);
-
-    // Don't display "all artwork by..." for albums where there's
-    // only one unique artwork in the first place.
-    if (tracksWithUniqueCoverArt.length > 1) {
-      const allCoverArtistArrays =
-        tracksWithUniqueCoverArt
-          .map(track => track.coverArtistContribs)
-          .map(contribs => contribs.map(contrib => contrib.artist));
-
-      const allSameCoverArtists =
-        allCoverArtistArrays
-          .slice(1)
-          .every(artists => compareArrays(artists, allCoverArtistArrays[0]));
-
-      if (allSameCoverArtists) {
-        query.coverArtistsForAllTracks =
-          allCoverArtistArrays[0];
-      }
-    }
+        .map(track => track.trackArtworks
+          .map(artwork => artwork.label));
+
+    const recurranceThreshold = 2;
+
+    // This list may include null, if some artworks are not labelled!
+    // That's expected.
+    query.recurringTrackArtworkLabels =
+      unique(trackArtworkLabels.flat())
+        .filter(label =>
+          trackArtworkLabels
+            .filter(labels => labels.includes(label))
+            .length >=
+          (label === null
+            ? 1
+            : recurranceThreshold));
 
     return query;
   },
 
-  relations(relation, query, album) {
-    const relations = {};
+  relations: (relation, query, album) => ({
+    layout:
+      relation('generatePageLayout'),
 
-    relations.layout =
-      relation('generatePageLayout');
+    albumStyleRules:
+      relation('generateAlbumStyleRules', album, null),
 
-    relations.albumStyleRules =
-      relation('generateAlbumStyleRules', album, null);
-
-    relations.albumLink =
-      relation('linkAlbum', album);
-
-    relations.albumNavAccent =
-      relation('generateAlbumNavAccent', album, null);
-
-    relations.secondaryNav =
-      relation('generateAlbumSecondaryNav', album);
-
-    relations.statsLine =
-      relation('generateAlbumGalleryStatsLine', album);
-
-    if (album.tracks.every(track => !track.hasUniqueCoverArt)) {
-      relations.noTrackArtworksLine =
-        relation('generateAlbumGalleryNoTrackArtworksLine');
-    }
-
-    if (query.coverArtistsForAllTracks) {
-      relations.coverArtistsLine =
-        relation('generateAlbumGalleryCoverArtistsLine', query.coverArtistsForAllTracks);
-    }
-
-    relations.coverGrid =
-      relation('generateCoverGrid');
-
-    relations.links = [
+    albumLink:
       relation('linkAlbum', album),
 
-      ...
-        album.tracks
-          .map(track => relation('linkTrack', track)),
-    ];
-
-    relations.images = [
-      (album.hasCoverArt
-        ? relation('image', album.artTags)
-        : relation('image')),
+    albumNavAccent:
+      relation('generateAlbumNavAccent', album, null),
 
-      ...
-        album.tracks.map(track =>
-          (track.hasUniqueCoverArt
-            ? relation('image', track.artTags)
-            : relation('image'))),
-    ];
-
-    return relations;
-  },
+    secondaryNav:
+      relation('generateAlbumSecondaryNav', album),
 
-  data(query, album) {
-    const data = {};
+    statsLine:
+      relation('generateAlbumGalleryStatsLine', album),
 
-    data.name = album.name;
-    data.color = album.color;
-
-    data.names = [
-      album.name,
-      ...album.tracks.map(track => track.name),
-    ];
-
-    data.coverArtists = [
-      (album.hasCoverArt
-        ? album.coverArtistContribs.map(({artist}) => artist.name)
+    noTrackArtworksLine:
+      (album.tracks.every(track => !track.hasUniqueCoverArt)
+        ? relation('generateAlbumGalleryNoTrackArtworksLine')
         : null),
 
-      ...
-        album.tracks.map(track => {
-          if (query.coverArtistsForAllTracks) {
-            return null;
-          }
+    setSwitcher:
+      relation('generateIntrapageDotSwitcher'),
 
-          if (track.hasUniqueCoverArt) {
-            return track.coverArtistContribs.map(({artist}) => artist.name);
-          }
+    albumGrid:
+      relation('generateAlbumGalleryAlbumGrid', album),
 
-          return null;
-        }),
-    ];
-
-    data.paths = [
-      (album.hasCoverArt
-        ? ['media.albumCover', album.directory, album.coverArtFileExtension]
-        : null),
+    trackGrids:
+      query.recurringTrackArtworkLabels.map(label =>
+        relation('generateAlbumGalleryTrackGrid', album, label)),
+  }),
 
-      ...
-        album.tracks.map(track =>
-          (track.hasUniqueCoverArt
-            ? ['media.trackCover', track.album.directory, track.directory, track.coverArtFileExtension]
-            : null)),
-    ];
+  data: (query, album) => ({
+    trackGridLabels:
+      query.recurringTrackArtworkLabels,
 
-    data.dimensions = [
-      (album.hasCoverArt
-        ? album.coverArtDimensions
-        : null),
+    trackGridIDs:
+      query.recurringTrackArtworkLabels.map(label =>
+        'track-grid-' +
+          (label
+            ? getKebabCase(label)
+            : 'no-label')),
 
-      ...
-        album.tracks.map(track =>
-          (track.hasUniqueCoverArt
-            ? track.coverArtDimensions
-            : null)),
-    ];
+    name:
+      album.name,
 
-    return data;
-  },
+    color:
+      album.color,
+  }),
 
-  generate: (data, relations, {language}) =>
+  generate: (data, relations, {html, language}) =>
     language.encapsulate('albumGalleryPage', pageCapsule =>
       relations.layout.slots({
         title:
@@ -176,34 +111,39 @@ export default {
         mainClasses: ['top-index'],
         mainContent: [
           relations.statsLine,
-          relations.coverArtistsLine,
+
+          relations.albumGrid,
+
           relations.noTrackArtworksLine,
 
-          relations.coverGrid
-            .slots({
-              links: relations.links,
-              names: data.names,
-              images:
-                stitchArrays({
-                  image: relations.images,
-                  path: data.paths,
-                  dimensions: data.dimensions,
-                  name: data.names,
-                }).map(({image, path, dimensions, name}) =>
-                    image.slots({
-                      path,
-                      dimensions,
-                      missingSourceContent:
-                        language.$('misc.albumGalleryGrid.noCoverArt', {name}),
-                    })),
-              info:
-                data.coverArtists.map(names =>
-                  (names === null
-                    ? null
-                    : language.$('misc.coverGrid.details.coverArtists', {
-                        artists: language.formatUnitList(names),
-                      }))),
-            }),
+          data.trackGridIDs.length > 1 &&
+            html.tag('p', {class: 'gallery-set-switcher'},
+              language.encapsulate(pageCapsule, 'setSwitcher', switcherCapsule =>
+                language.$(switcherCapsule, {
+                  sets:
+                    relations.setSwitcher.slots({
+                      initialOptionIndex: 0,
+
+                      titles:
+                        data.trackGridLabels.map(label =>
+                          label ??
+                          language.$(switcherCapsule, 'unlabeledSet')),
+
+                      targetIDs:
+                        data.trackGridIDs,
+                    }),
+                }))),
+
+          stitchArrays({
+            grid: relations.trackGrids,
+            id: data.trackGridIDs,
+          }).map(({grid, id}, index) =>
+              grid.slots({
+                attributes: [
+                  {id},
+                  index >= 1 && {style: 'display: none'},
+                ],
+              })),
         ],
 
         navLinkStyle: 'hierarchical',
diff --git a/src/content/dependencies/generateAlbumGalleryTrackGrid.js b/src/content/dependencies/generateAlbumGalleryTrackGrid.js
new file mode 100644
index 00000000..85e7576c
--- /dev/null
+++ b/src/content/dependencies/generateAlbumGalleryTrackGrid.js
@@ -0,0 +1,122 @@
+import {compareArrays, stitchArrays} from '#sugar';
+
+export default {
+  contentDependencies: [
+    'generateAlbumGalleryCoverArtistsLine',
+    'generateCoverGrid',
+    'image',
+    'linkAlbum',
+    'linkTrack',
+  ],
+
+  extraDependencies: ['html', 'language'],
+
+  query(album, label) {
+    const query = {};
+
+    query.artworks =
+      album.tracks.map(track =>
+        track.trackArtworks.find(artwork => artwork.label === label) ??
+        null);
+
+    const presentArtworks =
+      query.artworks.filter(Boolean);
+
+    if (presentArtworks.length > 1) {
+      const allArtistArrays =
+        presentArtworks
+          .map(artwork => artwork.artistContribs
+            .map(contrib => contrib.artist));
+
+      const allSameArtists =
+        allArtistArrays
+          .slice(1)
+          .every(artists => compareArrays(artists, allArtistArrays[0]));
+
+      if (allSameArtists) {
+        query.artistsForAllTrackArtworks =
+          allArtistArrays[0];
+      }
+    }
+
+    return query;
+  },
+
+  relations: (relation, query, album, _label) => ({
+    coverArtistsLine:
+      (query.artistsForAllTrackArtworks
+        ? relation('generateAlbumGalleryCoverArtistsLine',
+            query.artistsForAllTrackArtworks)
+        : null),
+
+    coverGrid:
+      relation('generateCoverGrid'),
+
+    albumLink:
+      relation('linkAlbum', album),
+
+    trackLinks:
+      album.tracks
+        .map(track => relation('linkTrack', track)),
+
+    images:
+      query.artworks
+        .map(artwork => relation('image', artwork)),
+  }),
+
+  data: (query, album, _label) => ({
+    trackNames:
+      album.tracks
+        .map(track => track.name),
+
+    trackArtworkArtists:
+      query.artworks.map(artwork =>
+        (query.artistsForAllTrackArtworks
+          ? null
+       : artwork
+          ? artwork.artistContribs
+              .map(contrib => contrib.artist.name)
+          : null)),
+  }),
+
+  slots: {
+    attributes: {type: 'attributes', mutable: false},
+  },
+
+  generate: (data, relations, slots, {html, language}) =>
+    html.tag('div',
+      {[html.onlyIfContent]: true},
+
+      slots.attributes,
+
+      [
+        relations.coverArtistsLine,
+
+        relations.coverGrid.slots({
+          links:
+            relations.trackLinks,
+
+          names:
+            data.trackNames,
+
+          images:
+            stitchArrays({
+              image: relations.images,
+              name: data.trackNames,
+            }).map(({image, name}) =>
+                image.slots({
+                  missingSourceContent:
+                    language.$('misc.albumGalleryGrid.noCoverArt', {name}),
+                })),
+
+          info:
+            data.trackArtworkArtists.map(artists =>
+              language.$('misc.coverGrid.details.coverArtists', {
+                [language.onlyIfOptions]: ['artists'],
+
+                artists:
+                  language.formatUnitList(artists),
+              })),
+        }),
+      ]),
+};
diff --git a/src/static/css/site.css b/src/static/css/site.css
index e4057620..ab86915c 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -1746,6 +1746,10 @@ ul.quick-info li:not(:last-child)::after {
   margin-top: 25px;
 }
 
+.gallery-set-switcher {
+  text-align: center;
+}
+
 .quick-description:not(.has-external-links-only) {
   --clamped-padding-ratio: max(var(--responsive-padding-ratio), 0.06);
   margin-left: auto;
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 751d8279..7d50dbb3 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -1108,6 +1108,15 @@ albumGalleryPage:
   noTrackArtworksLine: >-
     This album doesn't have any track artwork.
 
+  # setSwitcher:
+  #   This is displayed if multiple sets of artwork are available
+  #   across the album.
+
+  setSwitcher:
+    _: "({SETS})"
+
+    unlabeledSet: "Main album art"
+
 #
 # albumCommentaryPage:
 #   The album commentary page is a more minimal layout that brings