« get me outta code hell

content, css: group contributions table 2 - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2026-06-10 06:06:39 -0300
committer(quasar) nebula <qznebula@protonmail.com>2026-06-10 06:45:19 -0300
commit7710949c13b149d40195b4203b8a8234039ef5d6 (patch)
treeb5cf3e40f379a7e08a12dc54a08cdede03c6902e /src
parentf10f5a187f26d08019e452e7fbd417b7f462faa4 (diff)
content, css: group contributions table 2
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateArtistGroupContributionsInfo.js242
-rw-r--r--src/content/dependencies/generateArtistInfoPage.js44
-rw-r--r--src/static/css/features.css36
-rw-r--r--src/static/js/client/index.js2
-rw-r--r--src/static/js/group-contributions-table.js33
-rw-r--r--src/strings-default.yaml23
6 files changed, 100 insertions, 280 deletions
diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js
index 72ce0944..3c6187f9 100644
--- a/src/content/dependencies/generateArtistGroupContributionsInfo.js
+++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js
@@ -1,4 +1,4 @@
-import {accumulateSum, empty, stitchArrays, withEntries} from '#sugar';
+import {accumulateSum, stitchArrays, withEntries} from '#sugar';
 
 export default {
   sprawl: ({groupCategoryData}) => ({
@@ -45,196 +45,94 @@ export default {
           ([group, things]) =>
           ([group, accumulateSum(things, thing => thing.duration)])))
 
-    const groupsSortedByCount =
-      allGroupsOrdered
-        .filter(group => groupToTotalContributions.get(group) > 0)
-        .sort((a, b) =>
-          (groupToTotalContributions.get(b)
-         - groupToTotalContributions.get(a)));
-
-    const groupsSortedByDuration =
-      allGroupsOrdered
-        .filter(group => groupToTotalDuration.get(group) > 0)
-        .sort((a, b) =>
-          (groupToTotalDuration.get(b)
-         - groupToTotalDuration.get(a)));
-
-    const groupCountsSortedByCount =
-      groupsSortedByCount
-        .map(group => groupToTotalContributions.get(group));
-
-    const groupDurationsSortedByCount =
-      groupsSortedByCount
-        .map(group => groupToTotalDuration.get(group));
-
-    const groupDurationsApproximateSortedByCount =
-      groupsSortedByCount
-        .map(group => groupToThingsCountedForDuration.get(group).size > 1);
-
-    const groupCountsSortedByDuration =
-      groupsSortedByDuration
-        .map(group => groupToTotalContributions.get(group));
-
-    const groupDurationsSortedByDuration =
-      groupsSortedByDuration
-        .map(group => groupToTotalDuration.get(group));
-
-    const groupDurationsApproximateSortedByDuration =
-      groupsSortedByDuration
-        .map(group => groupToThingsCountedForDuration.get(group).size > 1);
-
     return {
-      groupsSortedByCount,
-      groupsSortedByDuration,
+      groups:
+        allGroupsOrdered,
 
-      groupCountsSortedByCount,
-      groupDurationsSortedByCount,
-      groupDurationsApproximateSortedByCount,
+      groupCounts:
+        allGroupsOrdered
+          .map(group => groupToTotalContributions.get(group)),
 
-      groupCountsSortedByDuration,
-      groupDurationsSortedByDuration,
-      groupDurationsApproximateSortedByDuration,
+      groupDurations:
+        allGroupsOrdered
+          .map(group => groupToTotalDuration.get(group)),
     };
   },
 
   relations: (relation, query) => ({
-    groupLinksSortedByCount:
-      query.groupsSortedByCount
-        .map(group => relation('linkGroup', group)),
-
-    groupLinksSortedByDuration:
-      query.groupsSortedByDuration
+    groupLinks:
+      query.groups
         .map(group => relation('linkGroup', group)),
   }),
 
   data: (query) => ({
-    groupCountsSortedByCount:
-      query.groupCountsSortedByCount,
+    hasCountColumn:
+      true,
 
-    groupDurationsSortedByCount:
-      query.groupDurationsSortedByCount,
+    hasDurationColumn:
+      query.groupDurations.some(Boolean),
 
-    groupDurationsApproximateSortedByCount:
-      query.groupDurationsApproximateSortedByCount,
+    groupsChangeCategory:
+      query.groups
+        .map((group, index, groups) =>
+          index >= 1 &&
+          (group.category !==
+           groups[index - 1].category)),
 
-    groupCountsSortedByDuration:
-      query.groupCountsSortedByDuration,
+    groupCounts:
+      query.groupCounts,
 
-    groupDurationsSortedByDuration:
-      query.groupDurationsSortedByDuration,
-
-    groupDurationsApproximateSortedByDuration:
-      query.groupDurationsApproximateSortedByDuration,
+    groupDurations:
+      query.groupDurations,
   }),
 
   slots: {
-    title: {
-      type: 'html',
-      mutable: false,
-    },
-
-    showBothColumns: {type: 'boolean'},
-    showSortButton: {type: 'boolean'},
-    visible: {type: 'boolean', default: true},
-
-    sort: {validate: v => v.is('count', 'duration')},
-    countUnit: {validate: v => v.is('tracks', 'artworks')},
+    string: {type: 'string'},
   },
 
   generate: (data, relations, slots, {html, language}) =>
-    language.encapsulate('artistPage.groupContributions', capsule => {
-      if (slots.sort === 'count' && empty(relations.groupLinksSortedByCount)) {
-        return html.blank();
-      } else if (slots.sort === 'duration' && empty(relations.groupLinksSortedByDuration)) {
-        return html.blank();
-      }
-
-      const getCounts = counts =>
-        counts.map(count => {
-          switch (slots.countUnit) {
-            case 'tracks': return language.countTracks(count, {unit: true});
-            case 'artworks': return language.countArtworks(count, {unit: true});
-          }
-        });
-
-      // We aren't displaying the "~" approximate symbol here for now.
-      // The general notion that these sums aren't going to be 100% accurate
-      // is made clear by the "XYZ has contributed ~1:23:45 hours of music..."
-      // line that's always displayed above this table.
-      const getDurations = (durations, approximate) =>
-        stitchArrays({
-          duration: durations,
-          approximate: approximate,
-        }).map(({duration}) => language.formatDuration(duration));
-
-      const topLevelClasses = [
-        'group-contributions-sorted-by-' + slots.sort,
-        slots.visible && 'visible',
-      ];
-
-      // TODO: It feels pretty awkward that this component is the only one that
-      // has enough knowledge to decide if the sort button is even applicable...
-      const switchingSortPossible =
-        !empty(relations.groupLinksSortedByCount) &&
-        !empty(relations.groupLinksSortedByDuration);
-
-      return html.tags([
-        html.tag('dt', {class: topLevelClasses},
-          language.encapsulate(capsule, 'title', capsule =>
-            (switchingSortPossible && slots.showSortButton
-              ? language.$(capsule, 'withSortButton', {
-                  title: slots.title,
-                  sort:
-                    html.tag('a', {class: 'group-contributions-sort-button'},
-                      {href: '#'},
-
-                      (slots.sort === 'count'
-                        ? language.$(capsule, 'sorting.count')
-                        : language.$(capsule, 'sorting.duration'))),
-                })
-              : slots.title))),
-
-        html.tag('dd', {class: topLevelClasses},
-          html.tag('table', {class: 'group-contributions-table'},
-            (stitchArrays(
-              (slots.sort === 'count'
-                ? {
-                    group: relations.groupLinksSortedByCount,
-                    count: getCounts(data.groupCountsSortedByCount),
-                    duration:
-                      getDurations(
-                        data.groupDurationsSortedByCount,
-                        data.groupDurationsApproximateSortedByCount),
-                  }
-                : {
-                    group: relations.groupLinksSortedByDuration,
-                    count: getCounts(data.groupCountsSortedByDuration),
-                    duration:
-                      getDurations(
-                        data.groupDurationsSortedByDuration,
-                        data.groupDurationsApproximateSortedByDuration),
-                  })
-            )).map(({group, count, duration}) =>
-                language.encapsulate(capsule, 'item', capsule =>
-                  html.tag('tr', [
-                    html.tag('td', {class: 'group-contributions-link-cell'},
-                      html.tag('span', group)),
-
-                    html.tag('td', {class: 'group-contributions-metrics-cell'},
-                      (slots.sort === 'count'
-                          // When sorting by count, duration details aren't necessarily
-                          // available for all items.
-                        ? (slots.showBothColumns && duration
-                            ? language.$(capsule, 'countDurationAccent', {count, duration})
-                            : language.$(capsule, 'countAccent', {count}))
-
-                          // Count details are always available, since they're just the
-                          // number of contributions directly. And duration details are
-                          // guaranteed for every item when sorting by duration.
-                        : (slots.showBothColumns
-                            ? language.$(capsule, 'durationCountAccent', {duration, count})
-                            : language.$(capsule, 'durationAccent', {duration})))),
-                  ]))))),
-      ]);
-    }),
+    language.encapsulate('artistPage.groupContributions', capsule =>
+      html.tag('table', {class: 'group-contributions-table'},
+        {[html.onlyIfContent]: true},
+
+        [
+          html.tag('thead',
+            {[html.onlyIfSiblings]: true},
+
+            html.tag('tr', [
+              html.tag('th',
+                language.$(capsule, 'title', slots.string)),
+
+              data.hasCountColumn &&
+                html.tag('th',
+                  language.$(capsule, 'column.count', slots.string)),
+
+              data.hasDurationColumn &&
+                html.tag('th',
+                  language.$(capsule, 'column.duration')),
+            ])),
+
+          html.tag('tbody',
+            {[html.onlyIfContent]: true},
+
+            stitchArrays({
+              link: relations.groupLinks,
+              changesCategory: data.groupsChangeCategory,
+              count: data.groupCounts,
+              duration: data.groupDurations,
+            }).map(({link, changesCategory, count, duration}) =>
+                html.tag('tr', changesCategory && {class: 'split'}, [
+                  html.tag('td', {class: 'group'},
+                    link),
+
+                  data.hasCountColumn &&
+                    html.tag('td', {class: 'count'},
+                      count),
+
+                  data.hasDurationColumn &&
+                    html.tag('td', {class: 'duration'},
+                      language.formatDuration(duration)),
+                ]),
+              )),
+        ])),
 };
diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js
index c3ac0b9f..bf4010a2 100644
--- a/src/content/dependencies/generateArtistInfoPage.js
+++ b/src/content/dependencies/generateArtistInfoPage.js
@@ -285,25 +285,7 @@ export default {
 
             relations.tracksChunkedList.slots({
               groupInfo:
-                language.encapsulate(pageCapsule, 'groupContributions', capsule => [
-                  relations.tracksGroupInfo.clone()
-                    .slots({
-                      title: language.$(capsule, 'title.music'),
-                      showSortButton: true,
-                      sort: 'count',
-                      countUnit: 'tracks',
-                      visible: true,
-                    }),
-
-                  relations.tracksGroupInfo.clone()
-                    .slots({
-                      title: language.$(capsule, 'title.music'),
-                      showSortButton: true,
-                      sort: 'duration',
-                      countUnit: 'tracks',
-                      visible: false,
-                    }),
-                ]),
+                relations.tracksGroupInfo.slot('string', 'tracks'),
             }),
           ]),
 
@@ -328,18 +310,10 @@ export default {
                     }),
                 }))),
 
-            relations.artworksChunkedList
-              .slots({
-                groupInfo:
-                  language.encapsulate(pageCapsule, 'groupContributions', capsule =>
-                    relations.artworksGroupInfo
-                      .slots({
-                        title: language.$(capsule, 'title.artworks'),
-                        showBothColumns: false,
-                        sort: 'count',
-                        countUnit: 'artworks',
-                      })),
-              }),
+            relations.artworksChunkedList.slots({
+              groupInfo:
+                relations.artworksGroupInfo.slot('string', 'artworks'),
+            }),
 
             html.tags([
               language.encapsulate(pageCapsule, 'wikiEditArtworks', capsule =>
@@ -368,13 +342,7 @@ export default {
 
             relations.musicVideosChunkedList.slots({
               groupInfo:
-                language.encapsulate(pageCapsule, 'groupContributions', capsule =>
-                  relations.musicVideosGroupInfo.slots({
-                    title: language.$(capsule, 'title.artworks'),
-                    showBothColumns: false,
-                    sort: 'count',
-                    countUnit: 'artworks',
-                  })),
+                relations.musicVideosGroupInfo.slot('string', 'musicVideos'),
             }),
           ]),
 
diff --git a/src/static/css/features.css b/src/static/css/features.css
index de615de2..2d54fe7a 100644
--- a/src/static/css/features.css
+++ b/src/static/css/features.css
@@ -1243,32 +1243,26 @@
   .group-contributions-table {
     margin-left: 40px;
     border-collapse: collapse;
-  }
 
-  .group-contributions-table td {
-    padding: 0 0 4px 0;
-  }
+    margin-bottom: 1em;
 
-  .group-contributions-table .group-contributions-link-cell {
-    display: list-item;
-  }
+    tr.split td { border-top: 2px solid #fff2; }
 
-  .group-contributions-table .group-contributions-metrics-cell {
-    padding-left: 1.5ch;
-    white-space: nowrap;
-    text-align: right;
-  }
-}
+    tr:has(+ .split) td { padding-bottom: 4px; }
+    tr.split td         { padding-top: 4px; }
 
-@layer interaction {
-  .group-contributions-sorted-by-count:not(.visible),
-  .group-contributions-sorted-by-duration:not(.visible) {
-    display: none;
-  }
+    td {
+      padding: 0 0 4px 0;
+    }
 
-  .group-contributions-sort-button {
-    text-decoration: underline;
-    text-decoration-style: dotted;
+    td:not(:last-child),
+    th:not(:last-child) {
+      padding-right: 1.5ch;
+    }
+
+    td.group { padding-left: 3ch; }
+    td.count { text-align: center; }
+    td.duration { text-align: right; }
   }
 }
 
diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js
index 65254b10..a17f7dab 100644
--- a/src/static/js/client/index.js
+++ b/src/static/js/client/index.js
@@ -1,5 +1,3 @@
-import '../group-contributions-table.js';
-
 import * as additionalNamesBoxModule from './additional-names-box.js';
 import * as albumCommentarySidebarModule from './album-commentary-sidebar.js';
 import * as artTagGalleryFilterModule from './art-tag-gallery-filter.js';
diff --git a/src/static/js/group-contributions-table.js b/src/static/js/group-contributions-table.js
deleted file mode 100644
index bef85fad..00000000
--- a/src/static/js/group-contributions-table.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// TODO: Update to clientSteps style.
-
-const groupContributionsTableInfo =
-  Array.from(document.querySelectorAll('#content dl'))
-    .filter(dl => dl.querySelector('a.group-contributions-sort-button'))
-    .map(dl => ({
-      sortingByCountLink: dl.querySelector('dt.group-contributions-sorted-by-count a.group-contributions-sort-button'),
-      sortingByDurationLink: dl.querySelector('dt.group-contributions-sorted-by-duration a.group-contributions-sort-button'),
-      sortingByCountElements: dl.querySelectorAll('.group-contributions-sorted-by-count'),
-      sortingByDurationElements: dl.querySelectorAll('.group-contributions-sorted-by-duration'),
-    }));
-
-function sortGroupContributionsTableBy(info, sort) {
-  const [showThese, hideThese] =
-    (sort === 'count'
-      ? [info.sortingByCountElements, info.sortingByDurationElements]
-      : [info.sortingByDurationElements, info.sortingByCountElements]);
-
-  for (const element of showThese) element.classList.add('visible');
-  for (const element of hideThese) element.classList.remove('visible');
-}
-
-for (const info of groupContributionsTableInfo) {
-  info.sortingByCountLink.addEventListener('click', evt => {
-    evt.preventDefault();
-    sortGroupContributionsTableBy(info, 'duration');
-  });
-
-  info.sortingByDurationLink.addEventListener('click', evt => {
-    evt.preventDefault();
-    sortGroupContributionsTableBy(info, 'count');
-  });
-}
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index cadd2f02..9a5f324e 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -1940,20 +1940,15 @@ artistPage:
 
   groupContributions:
     title:
-      music: "Contributed music to groups:"
-      artworks: "Contributed artworks to groups:"
-      musicVideos: "Contributed to music videos in groups:"
-      withSortButton: "{TITLE} ({SORT})"
-
-      sorting:
-        count: "Sorting by count."
-        duration: "Sorting by duration."
-
-    item:
-      countAccent: "({COUNT})"
-      durationAccent: "({DURATION})"
-      countDurationAccent: "({COUNT} — {DURATION})"
-      durationCountAccent: "({DURATION} — {COUNT})"
+      tracks: "Contributed music to group…"
+      artworks: "Contributed art to group…"
+      musicVideos: "Contributed to music videos in group…"
+
+    column:
+      count.tracks: "Tracks"
+      count.artworks: "Artworks"
+      count.musicVideos: "Music Videos"
+      duration: "Duration"
 
   trackList.title: "Tracks"
   artList.title: "Artworks"