« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things')
-rw-r--r--src/data/things/AdditionalFile.js56
-rw-r--r--src/data/things/Artist.js12
-rw-r--r--src/data/things/Language.js19
-rw-r--r--src/data/things/Track.js14
-rw-r--r--src/data/things/additional-file/AdditionalFile.js109
-rw-r--r--src/data/things/additional-file/MidiProjectFile.js28
-rw-r--r--src/data/things/additional-file/MiscellaneousAdditionalFile.js28
-rw-r--r--src/data/things/additional-file/SheetMusicFile.js28
-rw-r--r--src/data/things/additional-file/index.js5
-rw-r--r--src/data/things/album/Album.js21
-rw-r--r--src/data/things/index.js2
11 files changed, 246 insertions, 76 deletions
diff --git a/src/data/things/AdditionalFile.js b/src/data/things/AdditionalFile.js
deleted file mode 100644
index e3f309a6..00000000
--- a/src/data/things/AdditionalFile.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import {input} from '#composite';
-import Thing from '#thing';
-import {isString, validateArrayItems} from '#validators';
-
-import {exposeConstant, exposeUpdateValueOrContinue}
-  from '#composite/control-flow';
-import {contentString, simpleString, thing} from '#composite/wiki-properties';
-
-export class AdditionalFile extends Thing {
-  static [Thing.friendlyName] = `Additional File`;
-
-  static [Thing.getPropertyDescriptors] = () => ({
-    // Update & expose
-
-    thing: thing(),
-
-    title: simpleString(),
-
-    description: contentString(),
-
-    filenames: [
-      exposeUpdateValueOrContinue({
-        validate: input.value(validateArrayItems(isString)),
-      }),
-
-      exposeConstant({
-        value: input.value([]),
-      }),
-    ],
-
-    // Expose only
-
-    isAdditionalFile: [
-      exposeConstant({
-        value: input.value(true),
-      }),
-    ],
-  });
-
-  static [Thing.yamlDocumentSpec] = {
-    fields: {
-      'Title': {property: 'title'},
-      'Description': {property: 'description'},
-      'Files': {property: 'filenames'},
-    },
-  };
-
-  get paths() {
-    if (!this.thing) return null;
-    if (!this.thing.getOwnAdditionalFilePath) return null;
-
-    return (
-      this.filenames.map(filename =>
-        this.thing.getOwnAdditionalFilePath(this, filename)));
-  }
-}
diff --git a/src/data/things/Artist.js b/src/data/things/Artist.js
index 64798527..89da3c88 100644
--- a/src/data/things/Artist.js
+++ b/src/data/things/Artist.js
@@ -273,6 +273,18 @@ export class Artist extends Thing {
       exposeDependency('#otherArtistContributions'),
     ],
 
+    miscellaneousAdditionalFileArtistContributions: reverseReferenceList({
+      reverse: soupyReverse.input('miscellaneousAdditionalFileArtistContributionsBy'),
+    }),
+
+    sheetMusicFileArtistContributions: reverseReferenceList({
+      reverse: soupyReverse.input('sheetMusicFileArtistContributionsBy'),
+    }),
+
+    midiProjectFileArtistContributions: reverseReferenceList({
+      reverse: soupyReverse.input('midiProjectFileArtistContributionsBy'),
+    }),
+
     totalDuration: [
       withPropertyFromList('musicContributions', V('thing')),
       withPropertyFromList('#musicContributions.thing', V('isMainRelease')),
diff --git a/src/data/things/Language.js b/src/data/things/Language.js
index 2df58d19..5265d851 100644
--- a/src/data/things/Language.js
+++ b/src/data/things/Language.js
@@ -955,6 +955,7 @@ export class Language extends Thing {
 const countHelper = (stringKey, optionName = stringKey) =>
   function(value, {
     unit = false,
+    unitOnly = false,
     blankIfZero = false,
   } = {}) {
     // Null or undefined value is blank content.
@@ -967,22 +968,30 @@ const countHelper = (stringKey, optionName = stringKey) =>
       return html.blank();
     }
 
-    return this.formatString(
-      unit
+    const string =
+      (unitOnly
+        ? `count.${stringKey}.unitOnly.` + this.getUnitForm(value)
+     : unit
         ? `count.${stringKey}.withUnit.` + this.getUnitForm(value)
-        : `count.${stringKey}`,
-      {[optionName]: this.formatNumber(value)});
+        : `count.${stringKey}`);
+
+    const options =
+      (unitOnly
+        ? {}
+        : {[optionName]: this.formatNumber(value)});
+
+    return this.formatString(string, options);
   };
 
 // TODO: These are hard-coded. Is there a better way?
 Object.assign(Language.prototype, {
-  countAdditionalFiles: countHelper('additionalFiles', 'files'),
   countAlbums: countHelper('albums'),
   countArtTags: countHelper('artTags', 'tags'),
   countArtworks: countHelper('artworks'),
   countCommentaryEntries: countHelper('commentaryEntries', 'entries'),
   countContributions: countHelper('contributions'),
   countDays: countHelper('days'),
+  countFiles: countHelper('files'),
   countFlashes: countHelper('flashes'),
   countMonths: countHelper('months'),
   countTimesFeatured: countHelper('timesFeatured'),
diff --git a/src/data/things/Track.js b/src/data/things/Track.js
index 8752b2bb..c47729e9 100644
--- a/src/data/things/Track.js
+++ b/src/data/things/Track.js
@@ -37,8 +37,10 @@ import {
   parseDimensions,
   parseDuration,
   parseLyrics,
+  parseMidiProjectFiles,
   parseMusicVideos,
   parseReferencingSources,
+  parseSheetMusicFiles,
   parseURLs,
 } from '#yaml';
 
@@ -1123,12 +1125,12 @@ export class Track extends Thing {
 
       'Sheet Music Files': {
         property: 'sheetMusicFiles',
-        transform: parseAdditionalFiles,
+        transform: parseSheetMusicFiles,
       },
 
       'MIDI Project Files': {
         property: 'midiProjectFiles',
-        transform: parseAdditionalFiles,
+        transform: parseMidiProjectFiles,
       },
 
       // Content entries
@@ -1369,14 +1371,10 @@ export class Track extends Thing {
     },
   };
 
-  getOwnAdditionalFilePath(_file, filename) {
+  getOwnAdditionalFilePath(file, filename) {
     if (!this.album) return null;
 
-    return [
-      'media.albumAdditionalFile',
-      this.album.directory,
-      filename,
-    ];
+    return this.album.getOwnAdditionalFilePath(file, filename);
   }
 
   getOwnArtworkPath(artwork) {
diff --git a/src/data/things/additional-file/AdditionalFile.js b/src/data/things/additional-file/AdditionalFile.js
new file mode 100644
index 00000000..d137c741
--- /dev/null
+++ b/src/data/things/additional-file/AdditionalFile.js
@@ -0,0 +1,109 @@
+import {inspect} from 'node:util';
+
+import {colors} from '#cli';
+import {input, V} from '#composite';
+import Thing from '#thing';
+import {isString, validateArrayItems} from '#validators';
+import {parseContributors} from '#yaml';
+
+import {exposeConstant, exposeUpdateValueOrContinue}
+  from '#composite/control-flow';
+import {contributionList, contentString, simpleString, soupyFind, thing}
+  from '#composite/wiki-properties';
+
+export class AdditionalFile extends Thing {
+  static [Thing.friendlyName] = `Additional File`;
+
+  static [Thing.getPropertyDescriptors] = () => ({
+    // Update & expose
+
+    thing: thing(),
+
+    title: simpleString(),
+
+    description: contentString(),
+
+    folder: simpleString(),
+
+    filenames: [
+      exposeUpdateValueOrContinue({
+        validate: input.value(validateArrayItems(isString)),
+      }),
+
+      exposeConstant(V([])),
+    ],
+
+    artistContribs: contributionList({
+      // Subclasses override with the relevant artistProperty.
+      artistProperty: input.value(null),
+    }),
+
+    // Update only
+
+    find: soupyFind(),
+
+    // Expose only
+
+    isAdditionalFile: exposeConstant(V(true)),
+
+    // The date property is generally expected by contributions.
+    // Additional files don't actually support dates, but provide a null
+    // value for convenience.
+    date: {
+      flags: {expose: true},
+      expose: {compute: () => null},
+    },
+  });
+
+  static [Thing.yamlDocumentSpec] = {
+    fields: {
+      'Title': {property: 'title'},
+      'Description': {property: 'description'},
+
+      'Artists': {
+        property: 'artistContribs',
+        transform: parseContributors,
+      },
+
+      'Folder': {property: 'folder'},
+      'Files': {property: 'filenames'},
+    },
+  };
+
+  get paths() {
+    if (!this.thing) return null;
+    if (!this.thing.getOwnAdditionalFilePath) return null;
+
+    return (
+      this.filenames.map(filename =>
+        this.thing.getOwnAdditionalFilePath(this, filename)));
+  }
+
+  [inspect.custom](depth, options, inspect) {
+    const parts = [];
+
+    parts.push(this.constructor.name);
+
+    if (this.title) {
+      parts.push(` ${colors.green(`"${this.title}"`)}`);
+    }
+
+    if (this.thing) {
+      if (depth >= 0) {
+        const newOptions = {
+          ...options,
+          depth:
+            (options.depth === null
+              ? null
+              : options.depth - 1),
+        };
+
+        parts.push(` for ${inspect(this.thing, newOptions)}`);
+      } else {
+        parts.push(` for ${colors.blue(Thing.inspectReference(this.thing))}`);
+      }
+    }
+
+    return parts.join('');
+  }
+}
diff --git a/src/data/things/additional-file/MidiProjectFile.js b/src/data/things/additional-file/MidiProjectFile.js
new file mode 100644
index 00000000..8e7c19ca
--- /dev/null
+++ b/src/data/things/additional-file/MidiProjectFile.js
@@ -0,0 +1,28 @@
+import {input, V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+import {contributionList, soupyReverse} from '#composite/wiki-properties';
+
+import {AdditionalFile} from './AdditionalFile.js';
+
+export class MidiProjectFile extends AdditionalFile {
+  static [Thing.wikiData] = 'midiProjectFileData';
+
+  static [Thing.getPropertyDescriptors] = () => ({
+    // Update & expose
+
+    artistContribs: contributionList({
+      artistProperty: input.value('midiProjectFileArtistContributions'),
+    }),
+
+    // Expose only
+
+    isMidiProjectFile: exposeConstant(V(true)),
+  });
+
+  static [Thing.reverseSpecs] = {
+    midiProjectFileArtistContributionsBy:
+      soupyReverse.contributionsBy('midiProjectFileData', 'artistContribs'),
+  };
+}
diff --git a/src/data/things/additional-file/MiscellaneousAdditionalFile.js b/src/data/things/additional-file/MiscellaneousAdditionalFile.js
new file mode 100644
index 00000000..0110f830
--- /dev/null
+++ b/src/data/things/additional-file/MiscellaneousAdditionalFile.js
@@ -0,0 +1,28 @@
+import {input, V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+import {contributionList, soupyReverse} from '#composite/wiki-properties';
+
+import {AdditionalFile} from './AdditionalFile.js';
+
+export class MiscellaneousAdditionalFile extends AdditionalFile {
+  static [Thing.wikiData] = 'miscellaneousAdditionalFileData';
+
+  static [Thing.getPropertyDescriptors] = () => ({
+    // Update & expose
+
+    artistContribs: contributionList({
+      artistProperty: input.value('miscellaneousAdditionalFileArtistContributions'),
+    }),
+
+    // Expose only
+
+    isMiscellaneousAdditionalFile: exposeConstant(V(true)),
+  });
+
+  static [Thing.reverseSpecs] = {
+    miscellaneousAdditionalFileArtistContributionsBy:
+      soupyReverse.contributionsBy('miscellaneousAdditionalFileData', 'artistContribs'),
+  };
+}
diff --git a/src/data/things/additional-file/SheetMusicFile.js b/src/data/things/additional-file/SheetMusicFile.js
new file mode 100644
index 00000000..c06cde7f
--- /dev/null
+++ b/src/data/things/additional-file/SheetMusicFile.js
@@ -0,0 +1,28 @@
+import {input, V} from '#composite';
+import Thing from '#thing';
+
+import {exposeConstant} from '#composite/control-flow';
+import {contributionList, soupyReverse} from '#composite/wiki-properties';
+
+import {AdditionalFile} from './AdditionalFile.js';
+
+export class SheetMusicFile extends AdditionalFile {
+  static [Thing.wikiData] = 'sheetMusicFileData';
+
+  static [Thing.getPropertyDescriptors] = () => ({
+    // Update & expose
+
+    artistContribs: contributionList({
+      artistProperty: input.value('sheetMusicFileArtistContributions'),
+    }),
+
+    // Expose only
+
+    isSheetMusicFile: exposeConstant(V(true)),
+  });
+
+  static [Thing.reverseSpecs] = {
+    sheetMusicFileArtistContributionsBy:
+      soupyReverse.contributionsBy('sheetMusicFileData', 'artistContribs'),
+  };
+}
diff --git a/src/data/things/additional-file/index.js b/src/data/things/additional-file/index.js
new file mode 100644
index 00000000..d8de7455
--- /dev/null
+++ b/src/data/things/additional-file/index.js
@@ -0,0 +1,5 @@
+export * from './AdditionalFile.js';
+
+export * from './MidiProjectFile.js';
+export * from './MiscellaneousAdditionalFile.js';
+export * from './SheetMusicFile.js'
diff --git a/src/data/things/album/Album.js b/src/data/things/album/Album.js
index f07d552c..201aaf4e 100644
--- a/src/data/things/album/Album.js
+++ b/src/data/things/album/Album.js
@@ -852,12 +852,21 @@ export class Album extends Thing {
     ],
   };
 
-  getOwnAdditionalFilePath(_file, filename) {
-    return [
-      'media.albumAdditionalFile',
-      this.directory,
-      filename,
-    ];
+  getOwnAdditionalFilePath(file, filename) {
+    if (file.folder) {
+      return [
+        'media.albumAdditionalFileInFolder',
+        this.directory,
+        file.folder,
+        filename,
+      ];
+    } else {
+      return [
+        'media.albumAdditionalFile',
+        this.directory,
+        filename,
+      ];
+    }
   }
 
   getOwnArtworkPath(artwork) {
diff --git a/src/data/things/index.js b/src/data/things/index.js
index 3773864b..8cd21e9d 100644
--- a/src/data/things/index.js
+++ b/src/data/things/index.js
@@ -1,5 +1,6 @@
 // Not actually the entry point for #things - that's init.js in this folder.
 
+export * from './additional-file/index.js';
 export * from './album/index.js';
 export * from './content/index.js';
 export * from './contrib/index.js';
@@ -8,7 +9,6 @@ export * from './group/index.js';
 export * from './homepage-layout/index.js';
 export * from './sorting-rule/index.js';
 
-export * from './AdditionalFile.js';
 export * from './AdditionalName.js';
 export * from './ArtTag.js';
 export * from './Artist.js';