« get me outta code hell

content, data, client, css: style selector first pass - 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>2025-07-17 15:12:07 -0300
committer(quasar) nebula <qznebula@protonmail.com>2025-07-17 15:12:07 -0300
commitaf4ca039b42da9968e82087560eb398f3b3bbd17 (patch)
treeef1cfa2d805064c756faca3402a67a5c8f2257d1 /src
parent82a6098d64b17c7c21dd5c5db64b78da9a7e9bb3 (diff)
content, data, client, css: style selector first pass
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateCoverGrid.js10
-rw-r--r--src/content/dependencies/generateGroupGalleryPage.js5
-rw-r--r--src/content/dependencies/generateGroupGalleryPageAlbumGrid.js6
-rw-r--r--src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js22
-rw-r--r--src/content/dependencies/generateGroupGalleryPageStyleSelector.js62
-rw-r--r--src/data/things/group.js6
-rw-r--r--src/static/css/site.css41
-rw-r--r--src/static/js/client/gallery-style-selector.js123
-rw-r--r--src/static/js/client/index.js2
-rw-r--r--src/strings-default.yaml11
10 files changed, 283 insertions, 5 deletions
diff --git a/src/content/dependencies/generateCoverGrid.js b/src/content/dependencies/generateCoverGrid.js
index e1f13af3..e7113091 100644
--- a/src/content/dependencies/generateCoverGrid.js
+++ b/src/content/dependencies/generateCoverGrid.js
@@ -32,6 +32,12 @@ export default {
               v.isString))),
     },
 
+    itemAttributes: {
+      validate: v =>
+        v.strictArrayOf(
+          v.optional(v.isAttributes)),
+    },
+
     lazy: {validate: v => v.anyOf(v.isWholeNumber, v.isBoolean)},
     actionLinks: {validate: v => v.sparseArrayOf(v.isHTML)},
   },
@@ -44,6 +50,7 @@ export default {
       [
         stitchArrays({
           classes: slots.classes,
+          attributes: slots.itemAttributes,
           image: slots.images,
           link: slots.links,
           name: slots.names,
@@ -54,6 +61,7 @@ export default {
             Array.from(slots.links).fill(null)
         }).map(({
             classes,
+            attributes,
             image,
             link,
             name,
@@ -66,6 +74,8 @@ export default {
 
                 {class: ['grid-item', 'box']},
 
+                attributes,
+
                 (classes
                   ? {class: classes}
                   : null),
diff --git a/src/content/dependencies/generateGroupGalleryPage.js b/src/content/dependencies/generateGroupGalleryPage.js
index dfdad0e8..8e11f9e5 100644
--- a/src/content/dependencies/generateGroupGalleryPage.js
+++ b/src/content/dependencies/generateGroupGalleryPage.js
@@ -183,7 +183,10 @@ export default {
                 }))),
           */
 
-          relations.albumsByDateView,
+          relations.albumsByDateView.slots({
+            showTitle:
+              !html.isBlank(relations.albumsBySeriesView),
+          }),
 
           relations.albumsBySeriesView.slots({
             attributes: [
diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js b/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js
index 96cadb03..4f8aaf3b 100644
--- a/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js
+++ b/src/content/dependencies/generateGroupGalleryPageAlbumGrid.js
@@ -33,6 +33,9 @@ export default {
     tracks:
       albums.map(album => album.tracks.length),
 
+    styles:
+      albums.map(album => album.style),
+
     notFromThisGroup:
       albums.map(album => !album.groups.includes(group)),
   }),
@@ -56,6 +59,9 @@ export default {
                   }),
               })),
 
+        itemAttributes:
+          data.styles.map(style => ({'data-style': style})),
+
         info:
           stitchArrays({
             tracks: data.tracks,
diff --git a/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js b/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js
index b7d01eb5..58375f3e 100644
--- a/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js
+++ b/src/content/dependencies/generateGroupGalleryPageAlbumsByDateView.js
@@ -1,7 +1,11 @@
 import {sortChronologically} from '#sort';
 
 export default {
-  contentDependencies: ['generateGroupGalleryPageAlbumGrid'],
+  contentDependencies: [
+    'generateGroupGalleryPageAlbumGrid',
+    'generateGroupGalleryPageStyleSelector',
+  ],
+
   extraDependencies: ['html', 'language'],
 
   query: (group) => ({
@@ -10,6 +14,11 @@ export default {
   }),
 
   relations: (relation, query, group) => ({
+    styleSelector:
+      (group.divideAlbumsByStyle
+        ? relation('generateGroupGalleryPageStyleSelector', group)
+        : null),
+
     albumGrid:
       relation('generateGroupGalleryPageAlbumGrid',
         query.albums,
@@ -17,6 +26,10 @@ export default {
   }),
 
   slots: {
+    showTitle: {
+      type: 'boolean',
+    },
+
     attributes: {
       type: 'attributes',
       mutable: false,
@@ -31,8 +44,11 @@ export default {
         {[html.onlyIfContent]: true},
 
         html.tag('section', [
-          html.tag('h2',
-            language.$(capsule, 'title')),
+          slots.showTitle &&
+            html.tag('h2',
+              language.$(capsule, 'title')),
+
+          relations.styleSelector,
 
           relations.albumGrid,
         ]))),
diff --git a/src/content/dependencies/generateGroupGalleryPageStyleSelector.js b/src/content/dependencies/generateGroupGalleryPageStyleSelector.js
new file mode 100644
index 00000000..4f9d02a9
--- /dev/null
+++ b/src/content/dependencies/generateGroupGalleryPageStyleSelector.js
@@ -0,0 +1,62 @@
+import {unique} from '#sugar';
+
+export default {
+  extraDependencies: ['html', 'language'],
+
+  query: (group) => ({
+    styles:
+      unique(group.albums.map(album => album.style)),
+  }),
+
+  data: (query, group) => ({
+    albums:
+      group.albums.length,
+
+    styles:
+      query.styles,
+  }),
+
+  generate: (data, {html, language}) =>
+    language.encapsulate('groupGalleryPage', pageCapsule =>
+      (data.styles.length <= 1
+        ? html.blank()
+        : html.tag('p', {class: 'gallery-style-selector'},
+            {class: ['drop', 'shiny']},
+
+            language.encapsulate(pageCapsule, 'albumStyleSwitcher', capsule => [
+              html.tag('span',
+                language.$(capsule)),
+
+              html.tag('br'),
+
+              html.tag('span', {class: 'styles'},
+                data.styles.map(style =>
+                  html.tag('label', {'data-style': style}, [
+                    html.tag('input', {type: 'checkbox'},
+                      {checked: true}),
+
+                    html.tag('span',
+                      language.$(capsule, style)),
+                  ]))),
+
+              html.tag('br'),
+
+              html.tag('span', {class: ['count', 'all']},
+                language.$(capsule, 'count.all', {
+                  total: data.albums,
+                })),
+
+              html.tag('span', {class: ['count', 'filtered']},
+                {style: 'display: none'},
+
+                language.$(capsule, 'count.filtered', {
+                  count: html.tag('span'),
+                  total: data.albums,
+                })),
+
+              html.tag('span', {class: ['count', 'none']},
+                {style: 'display: none'},
+
+                language.$(capsule, 'count.none')),
+            ])))),
+};
diff --git a/src/data/things/group.js b/src/data/things/group.js
index fc33a9d0..bc2e915c 100644
--- a/src/data/things/group.js
+++ b/src/data/things/group.js
@@ -15,6 +15,7 @@ import {
   color,
   contentString,
   directory,
+  flag,
   name,
   referenceList,
   soupyFind,
@@ -32,6 +33,8 @@ export class Group extends Thing {
     name: name('Unnamed Group'),
     directory: directory(),
 
+    divideAlbumsByStyle: flag(false),
+
     description: contentString(),
 
     urls: urls(),
@@ -141,6 +144,9 @@ export class Group extends Thing {
     fields: {
       'Group': {property: 'name'},
       'Directory': {property: 'directory'},
+
+      'Divide Albums By Style': {property: 'divideAlbumsByStyle'},
+
       'Description': {property: 'description'},
       'URLs': {property: 'urls'},
 
diff --git a/src/static/css/site.css b/src/static/css/site.css
index e647dd83..2c5d6ce1 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -1130,6 +1130,15 @@ a .normal-content {
   text-decoration: none !important;
 }
 
+label:hover span {
+  text-decoration: underline;
+  text-decoration-style: solid;
+}
+
+label > input[type=checkbox]:not(:checked) + span {
+  opacity: 0.8;
+}
+
 #secondary-nav {
   text-align: center;
 
@@ -2021,13 +2030,32 @@ ul.quick-info li:not(:last-child)::after {
   text-align: center;
 }
 
-.gallery-view-switcher {
+.gallery-view-switcher,
+.gallery-style-selector {
   margin-left: auto;
   margin-right: auto;
   text-align: center;
   line-height: 1.4;
 }
 
+.gallery-style-selector .styles {
+  display: inline-flex;
+  justify-content: center;
+}
+
+.gallery-style-selector .styles label:not(:last-child) {
+  margin-right: 1.25ch;
+}
+
+.gallery-style-selector .count {
+  font-size: 0.85em;
+
+  position: relative;
+  bottom: -0.25em;
+
+  opacity: 0.9;
+}
+
 #content.top-index section {
   margin-bottom: 1.5em;
 }
@@ -3028,6 +3056,13 @@ video.pixelate, .pixelate video {
   box-sizing: border-box;
 }
 
+.grid-listing:not(:has(.grid-item:not([class*="hidden-by-"]))) {
+  padding-bottom: 140px;
+  background: #cccccc07;
+  border-radius: 10px;
+  border: 1px dashed #fff3;
+}
+
 .grid-item {
   font-size: 0.9em;
 }
@@ -3042,6 +3077,10 @@ video.pixelate, .pixelate video {
   margin: 10px;
 }
 
+.grid-item[class*="hidden-by-"] {
+  display: none;
+}
+
 .grid-item .image-container {
   width: 100%;
 }
diff --git a/src/static/js/client/gallery-style-selector.js b/src/static/js/client/gallery-style-selector.js
new file mode 100644
index 00000000..c7086eae
--- /dev/null
+++ b/src/static/js/client/gallery-style-selector.js
@@ -0,0 +1,123 @@
+/* eslint-env browser */
+
+import {cssProp} from '../client-util.js';
+
+import {stitchArrays} from '../../shared-util/sugar.js';
+
+export const info = {
+  id: 'galleryStyleSelectorInfo',
+
+  selectors: null,
+  sections: null,
+
+  selectorStyleInputs: null,
+  selectorStyleInputStyles: null,
+
+  selectorReleaseItems: null,
+  selectorReleaseItemStyles: null,
+
+  selectorCountAll: null,
+  selectorCountFiltered: null,
+  selectorCountFilteredCount: null,
+  selectorCountNone: null,
+};
+
+export function getPageReferences() {
+  info.selectors =
+    Array.from(document.querySelectorAll('.gallery-style-selector'));
+
+  info.sections =
+    info.selectors
+      .map(selector => selector.closest('section'));
+
+  info.selectorStyleInputs =
+    info.selectors
+      .map(selector => selector.querySelectorAll('.styles input'))
+      .map(inputs => Array.from(inputs));
+
+  info.selectorStyleInputStyles =
+    info.selectorStyleInputs
+      .map(inputs => inputs
+        .map(input => input.closest('label').dataset.style));
+
+  info.selectorReleaseItems =
+    info.sections
+      .map(section => section.querySelectorAll('.grid-item'))
+      .map(items => Array.from(items));
+
+  info.selectorReleaseItemStyles =
+    info.selectorReleaseItems
+      .map(items => items
+        .map(item => item.dataset.style));
+
+  info.selectorCountAll =
+    info.selectors
+      .map(selector => selector.querySelector('.count.all'));
+
+  info.selectorCountFiltered =
+    info.selectors
+      .map(selector => selector.querySelector('.count.filtered'));
+
+  info.selectorCountFilteredCount =
+    info.selectorCountFiltered
+      .map(selector => selector.querySelector('span'));
+
+  info.selectorCountNone =
+    info.selectors
+      .map(selector => selector.querySelector('.count.none'));
+}
+
+export function addPageListeners() {
+  for (const index of info.selectors.keys()) {
+    for (const input of info.selectorStyleInputs[index]) {
+      input.addEventListener('input', () => updateVisibleReleases(index));
+    }
+  }
+}
+
+function updateVisibleReleases(index) {
+  const inputs = info.selectorStyleInputs[index];
+  const inputStyles = info.selectorStyleInputStyles[index];
+
+  const selectedStyles =
+    stitchArrays({input: inputs, style: inputStyles})
+      .filter(({input}) => input.checked)
+      .map(({style}) => style);
+
+  const releases = info.selectorReleaseItems[index];
+  const releaseStyles = info.selectorReleaseItemStyles[index];
+
+  let visible = 0;
+
+  stitchArrays({
+    release: releases,
+    style: releaseStyles,
+  }).forEach(({release, style}) => {
+      if (selectedStyles.includes(style)) {
+        release.classList.remove('hidden-by-style-mismatch');
+        visible++;
+      } else {
+        release.classList.add('hidden-by-style-mismatch');
+      }
+    });
+
+  const countAll = info.selectorCountAll[index];
+  const countFiltered = info.selectorCountFiltered[index];
+  const countFilteredCount = info.selectorCountFilteredCount[index];
+  const countNone = info.selectorCountNone[index];
+
+  if (visible === releases.length) {
+    cssProp(countAll, 'display', null);
+    cssProp(countFiltered, 'display', 'none');
+    cssProp(countNone, 'display', 'none');
+  } else if (visible === 0) {
+    cssProp(countAll, 'display', 'none');
+    cssProp(countFiltered, 'display', 'none');
+    cssProp(countNone, 'display', null);
+  } else {
+    cssProp(countAll, 'display', 'none');
+    cssProp(countFiltered, 'display', null);
+    cssProp(countNone, 'display', 'none');
+    countFilteredCount.innerHTML = visible;
+  }
+}
diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js
index 9d7eae86..4ca4700e 100644
--- a/src/static/js/client/index.js
+++ b/src/static/js/client/index.js
@@ -12,6 +12,7 @@ import * as cssCompatibilityAssistantModule from './css-compatibility-assistant.
 import * as datetimestampTooltipModule from './datetimestamp-tooltip.js';
 import * as draggedLinkModule from './dragged-link.js';
 import * as expandableGallerySectionModule from './expandable-gallery-section.js';
+import * as galleryStyleSelectorModule from './gallery-style-selector.js';
 import * as hashLinkModule from './hash-link.js';
 import * as hoverableTooltipModule from './hoverable-tooltip.js';
 import * as imageOverlayModule from './image-overlay.js';
@@ -36,6 +37,7 @@ export const modules = [
   datetimestampTooltipModule,
   draggedLinkModule,
   expandableGallerySectionModule,
+  galleryStyleSelectorModule,
   hashLinkModule,
   hoverableTooltipModule,
   imageOverlayModule,
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index eed7b9b7..026b88c9 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -1756,6 +1756,17 @@ groupGalleryPage:
     bySeries: "By series"
     byDate: "By date"
 
+  albumStyleSwitcher:
+    _: "Showing these releases:"
+
+    album: "Albums"
+    single: "Singles"
+
+    count:
+      all: "all {TOTAL}"
+      filtered: "{COUNT} of {TOTAL}"
+      none: "none at all"
+
   albumsByDate:
     title: "All albums"