« get me outta code hell

data: split group.js - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things/group
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2026-01-26 13:38:13 -0400
committer(quasar) nebula <qznebula@protonmail.com>2026-01-26 13:47:00 -0400
commitaa3cb2dd34780fd97873340c3faf7388993fa8d8 (patch)
tree110f77c2f333c251fe61ee51f4faad0b4d2547fd /src/data/things/group
parentec05edf5ad729f6a02618f88ca1cfe3ef6a6f0ea (diff)
data: split group.js
Diffstat (limited to 'src/data/things/group')
-rw-r--r--src/data/things/group/Group.js232
-rw-r--r--src/data/things/group/GroupCategory.js58
-rw-r--r--src/data/things/group/Series.js79
-rw-r--r--src/data/things/group/index.js3
4 files changed, 372 insertions, 0 deletions
diff --git a/src/data/things/group/Group.js b/src/data/things/group/Group.js
new file mode 100644
index 00000000..b065f9a3
--- /dev/null
+++ b/src/data/things/group/Group.js
@@ -0,0 +1,232 @@
+const GROUP_DATA_FILE = 'groups.yaml';
+
+import {input, V} from '#composite';
+import Thing from '#thing';
+import {isBoolean} from '#validators';
+import {parseAnnotatedReferences, parseSerieses} from '#yaml';
+
+import {withPropertyFromObject} from '#composite/data';
+import {withUniqueReferencingThing} from '#composite/wiki-data';
+
+import {
+  exposeConstant,
+  exposeDependencyOrContinue,
+  exposeUpdateValueOrContinue,
+} from '#composite/control-flow';
+
+import {
+  annotatedReferenceList,
+  contentString,
+  directory,
+  flag,
+  name,
+  referenceList,
+  soupyFind,
+  soupyReverse,
+  thingList,
+  urls,
+} from '#composite/wiki-properties';
+
+export class Group extends Thing {
+  static [Thing.referenceType] = 'group';
+  static [Thing.wikiData] = 'groupData';
+
+  static [Thing.getPropertyDescriptors] = ({Album, Artist, Series}) => ({
+    // Update & expose
+
+    name: name(V('Unnamed Group')),
+    directory: directory(),
+
+    excludeFromGalleryTabs: [
+      exposeUpdateValueOrContinue({
+        validate: input.value(isBoolean),
+      }),
+
+      withUniqueReferencingThing({
+        reverse: soupyReverse.input('groupCategoriesWhichInclude'),
+      }).outputs({
+        '#uniqueReferencingThing': '#category',
+      }),
+
+      withPropertyFromObject('#category', V('excludeGroupsFromGalleryTabs')),
+      exposeDependencyOrContinue('#category.excludeGroupsFromGalleryTabs'),
+
+      exposeConstant(V(false)),
+    ],
+
+    divideAlbumsByStyle: flag(V(false)),
+
+    description: contentString(),
+
+    urls: urls(),
+
+    closelyLinkedArtists: annotatedReferenceList({
+      class: input.value(Artist),
+      find: soupyFind.input('artist'),
+
+      reference: input.value('artist'),
+      thing: input.value('artist'),
+    }),
+
+    featuredAlbums: referenceList({
+      class: input.value(Album),
+      find: soupyFind.input('album'),
+    }),
+
+    serieses: thingList(V(Series)),
+
+    // Update only
+
+    find: soupyFind(),
+    reverse: soupyReverse(),
+
+    // Expose only
+
+    isGroup: exposeConstant(V(true)),
+
+    descriptionShort: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['description'],
+        compute: ({description}) =>
+          (description
+            ? description.split('<hr class="split">')[0]
+            : null),
+      },
+    },
+
+    albums: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['this', '_reverse'],
+        compute: ({this: group, _reverse: reverse}) =>
+          reverse.albumsWhoseGroupsInclude(group),
+      },
+    },
+
+    color: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['this', '_reverse'],
+        compute: ({this: group, _reverse: reverse}) =>
+          reverse.groupCategoriesWhichInclude(group, {unique: true})
+            ?.color,
+      },
+    },
+
+    category: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['this', '_reverse'],
+        compute: ({this: group, _reverse: reverse}) =>
+          reverse.groupCategoriesWhichInclude(group, {unique: true}) ??
+          null,
+      },
+    },
+  });
+
+  static [Thing.findSpecs] = {
+    group: {
+      referenceTypes: ['group', 'group-gallery'],
+      bindTo: 'groupData',
+    },
+  };
+
+  static [Thing.reverseSpecs] = {
+    groupsCloselyLinkedTo: {
+      bindTo: 'groupData',
+
+      referencing: group =>
+        group.closelyLinkedArtists
+          .map(({artist, ...referenceDetails}) => ({
+            group,
+            artist,
+            referenceDetails,
+          })),
+
+      referenced: ({artist}) => [artist],
+
+      tidy: ({group, referenceDetails}) =>
+        ({group, ...referenceDetails}),
+    },
+  };
+
+  static [Thing.yamlDocumentSpec] = {
+    fields: {
+      'Group': {property: 'name'},
+      'Directory': {property: 'directory'},
+
+      'Exclude From Gallery Tabs': {property: 'excludeFromGalleryTabs'},
+      'Divide Albums By Style': {property: 'divideAlbumsByStyle'},
+
+      'Description': {property: 'description'},
+      'URLs': {property: 'urls'},
+
+      'Closely Linked Artists': {
+        property: 'closelyLinkedArtists',
+        transform: value =>
+          parseAnnotatedReferences(value, {
+            referenceField: 'Artist',
+            referenceProperty: 'artist',
+          }),
+      },
+
+      'Featured Albums': {property: 'featuredAlbums'},
+
+      'Series': {
+        property: 'serieses',
+        transform: parseSerieses,
+      },
+
+      'Review Points': {ignore: true},
+    },
+  };
+
+  static [Thing.getYamlLoadingSpec] = ({
+    documentModes: {allInOne},
+    thingConstructors: {Group, GroupCategory},
+  }) => ({
+    title: `Process groups file`,
+    file: GROUP_DATA_FILE,
+
+    documentMode: allInOne,
+    documentThing: document =>
+      ('Category' in document
+        ? GroupCategory
+        : Group),
+
+    connect(results) {
+      let groupCategory;
+      let groupRefs = [];
+
+      if (results[0] && !(results[0] instanceof GroupCategory)) {
+        throw new Error(`Expected a category at top of group data file`);
+      }
+
+      for (const thing of results) {
+        if (thing instanceof GroupCategory) {
+          if (groupCategory) {
+            Object.assign(groupCategory, {groups: groupRefs});
+          }
+
+          groupCategory = thing;
+          groupRefs = [];
+        } else {
+          groupRefs.push(Thing.getReference(thing));
+        }
+      }
+
+      if (groupCategory) {
+        Object.assign(groupCategory, {groups: groupRefs});
+      }
+    },
+
+    // Groups aren't sorted at all, always preserving the order in the data
+    // file as-is.
+    sort: null,
+  });
+}
diff --git a/src/data/things/group/GroupCategory.js b/src/data/things/group/GroupCategory.js
new file mode 100644
index 00000000..daf31868
--- /dev/null
+++ b/src/data/things/group/GroupCategory.js
@@ -0,0 +1,58 @@
+import {input, V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+
+import {color, directory, flag, name, referenceList, soupyFind}
+  from '#composite/wiki-properties';
+
+export class GroupCategory extends Thing {
+  static [Thing.referenceType] = 'group-category';
+  static [Thing.friendlyName] = `Group Category`;
+  static [Thing.wikiData] = 'groupCategoryData';
+
+  static [Thing.getPropertyDescriptors] = ({Group}) => ({
+    // Update & expose
+
+    name: name(V('Unnamed Group Category')),
+    directory: directory(),
+
+    excludeGroupsFromGalleryTabs: flag(V(false)),
+
+    color: color(),
+
+    groups: referenceList({
+      class: input.value(Group),
+      find: soupyFind.input('group'),
+    }),
+
+    // Update only
+
+    find: soupyFind(),
+
+    // Expose only
+
+    isGroupCategory: exposeConstant(V(true)),
+  });
+
+  static [Thing.reverseSpecs] = {
+    groupCategoriesWhichInclude: {
+      bindTo: 'groupCategoryData',
+
+      referencing: groupCategory => [groupCategory],
+      referenced: groupCategory => groupCategory.groups,
+    },
+  };
+
+  static [Thing.yamlDocumentSpec] = {
+    fields: {
+      'Category': {property: 'name'},
+
+      'Color': {property: 'color'},
+
+      'Exclude Groups From Gallery Tabs': {
+        property: 'excludeGroupsFromGalleryTabs',
+      },
+    },
+  };
+}
diff --git a/src/data/things/group/Series.js b/src/data/things/group/Series.js
new file mode 100644
index 00000000..940fe575
--- /dev/null
+++ b/src/data/things/group/Series.js
@@ -0,0 +1,79 @@
+import {inspect} from 'node:util';
+
+import {colors} from '#cli';
+import {input, V} from '#composite';
+import Thing from '#thing';
+import {is} from '#validators';
+
+import {contentString, name, referenceList, soupyFind, thing}
+  from '#composite/wiki-properties';
+
+export class Series extends Thing {
+  static [Thing.wikiData] = 'seriesData';
+
+  static [Thing.getPropertyDescriptors] = ({Album, Group}) => ({
+    // Update & expose
+
+    name: name(V('Unnamed Series')),
+
+    showAlbumArtists: {
+      flags: {update: true, expose: true},
+      update: {
+        validate:
+          is('all', 'differing', 'none'),
+      },
+    },
+
+    description: contentString(),
+
+    group: thing(V(Group)),
+
+    albums: referenceList({
+      class: input.value(Album),
+      find: soupyFind.input('album'),
+    }),
+
+    // Update only
+
+    find: soupyFind(),
+  });
+
+  static [Thing.yamlDocumentSpec] = {
+    fields: {
+      'Name': {property: 'name'},
+
+      'Description': {property: 'description'},
+
+      'Show Album Artists': {property: 'showAlbumArtists'},
+
+      'Albums': {property: 'albums'},
+    },
+  };
+
+  [inspect.custom](depth, options, inspect) {
+    const parts = [];
+
+    parts.push(Thing.prototype[inspect.custom].apply(this));
+
+    if (depth >= 0) showGroup: {
+      let group = null;
+      try {
+        group = this.group;
+      } catch {
+        break showGroup;
+      }
+
+      const groupName = group.name;
+      const groupIndex = group.serieses.indexOf(this);
+
+      const num =
+        (groupIndex === -1
+          ? 'indeterminate position'
+          : `#${groupIndex + 1}`);
+
+      parts.push(` (${colors.yellow(num)} in ${colors.green(`"${groupName}"`)})`);
+    }
+
+    return parts.join('');
+  }
+}
diff --git a/src/data/things/group/index.js b/src/data/things/group/index.js
new file mode 100644
index 00000000..1723f136
--- /dev/null
+++ b/src/data/things/group/index.js
@@ -0,0 +1,3 @@
+export * from './Group.js';
+export * from './GroupCategory.js';
+export * from './Series.js';