« 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/thing.js51
-rw-r--r--src/data/things/track.js95
2 files changed, 92 insertions, 54 deletions
diff --git a/src/data/things/thing.js b/src/data/things/thing.js
index c870b89c..2af06904 100644
--- a/src/data/things/thing.js
+++ b/src/data/things/thing.js
@@ -1114,6 +1114,57 @@ export default class Thing extends CacheableObject {
       };
     },
 
+    // Exposes a dependency exactly as it is; this is typically the base of a
+    // composition which was created to serve as one property's descriptor.
+    // Since this serves as a base, specify {update: true} to indicate that
+    // the property as a whole updates (and some previous compositional step
+    // works with that update value).
+    //
+    // Please note that this *doesn't* verify that the dependency exists, so
+    // if you provide the wrong name or it hasn't been set by a previous
+    // compositional step, the property will be exposed as undefined instead
+    // of null.
+    //
+    expose: (dependency, {update = false} = {}) => ({
+      annotation: `Thing.composite.expose`,
+      flags: {expose: true, update},
+
+      expose: {
+        mapDependencies: {dependency},
+        compute: ({dependency}) => dependency,
+      },
+    }),
+
+    // Exposes the update value of an {update: true} property, or continues if
+    // it's unavailable. By default, "unavailable" means value === null, but
+    // set {mode: 'empty'} to
+    exposeUpdateValueOrContinue({mode = 'null'} = {}) {
+      if (mode !== 'null' && mode !== 'empty') {
+        throw new TypeError(`Expected mode to be null or empty`);
+      }
+
+      return {
+        annotation: `Thing.composite.exposeUpdateValueOrContinue`,
+        flags: {expose: true, compose: true},
+        expose: {
+          options: {mode},
+
+          transform(value, {'#options': {mode}}, continuation) {
+            const shouldContinue =
+              (mode === 'empty'
+                ? empty(value)
+                : value === null);
+
+            if (shouldContinue) {
+              return continuation();
+            } else {
+              return continuation.exit(value);
+            }
+          }
+        },
+      };
+    },
+
     // Resolves the contribsByRef contained in the provided dependency,
     // providing (named by the second argument) the result. "Resolving"
     // means mapping the "who" reference of each contribution to an artist
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 6c08aa01..15a48bb4 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -45,26 +45,9 @@ export class Track extends Thing {
     artTagsByRef: Thing.common.referenceList(ArtTag),
 
     color: Thing.composite.from(`Track.color`, [
-      {
-        flags: {expose: true, compose: true},
-        expose: {
-          transform: (color, {}, continuation) =>
-            color ?? continuation(),
-        },
-      },
-
-      Track.composite.withAlbumProperties({
-        properties: ['color'],
-      }),
-
-      {
-        flags: {update: true, expose: true},
-        update: {validate: isColor},
-        expose: {
-          dependencies: ['#album.color'],
-          compute: ({'#album.color': color}) => color,
-        },
-      },
+      Thing.composite.exposeUpdateValueOrContinue(),
+      Track.composite.withAlbumProperty('color'),
+      Thing.composite.expose('#album.color', {update: true}),
     ]),
 
     // Disables presenting the track as though it has its own unique artwork.
@@ -169,15 +152,11 @@ export class Track extends Thing {
 
     commentatorArtists: Thing.common.commentatorArtists(),
 
-    album: {
-      flags: {expose: true},
-
-      expose: {
-        dependencies: ['this', 'albumData'],
-        compute: ({this: track, albumData}) =>
-          albumData?.find((album) => album.tracks.includes(track)) ?? null,
-      },
-    },
+    album:
+      Thing.composite.from(`Track.album`, [
+        Track.composite.withAlbum(),
+        Thing.composite.expose('#album'),
+      ]),
 
     // Note - this is an internal property used only to help identify a track.
     // It should not be assumed in general that the album and dataSourceAlbum match
@@ -202,17 +181,8 @@ export class Track extends Thing {
         },
       },
 
-      Track.composite.withAlbumProperties({
-        properties: ['date'],
-      }),
-
-      {
-        flags: {expose: true},
-        expose: {
-          dependencies: ['#album.date'],
-          compute: ({'#album.date': date}) => date,
-        },
-      },
+      Track.composite.withAlbumProperties({properties: ['date']}),
+      Thing.composite.expose('#album.date'),
     ]),
 
     // Whether or not the track has "unique" cover artwork - a cover which is
@@ -369,20 +339,8 @@ export class Track extends Thing {
         },
       },
 
-      Track.composite.withAlbumProperties({
-        properties: ['trackCoverArtistContribs'],
-      }),
-
-      {
-        flags: {expose: true},
-        expose: {
-          mapDependencies: {contribsFromAlbum: '#album.trackCoverArtistContribs'},
-          compute: ({contribsFromAlbum}) =>
-            (empty(contribsFromAlbum)
-              ? null
-              : contribsFromAlbum),
-        },
-      },
+      Track.composite.withAlbumProperty('trackCoverArtistContribs'),
+      Thing.composite.expose('#album.trackCoverArtistContribs'),
     ]),
 
     referencedTracks: Thing.composite.from(`Track.referencedTracks`, [
@@ -513,6 +471,35 @@ export class Track extends Thing {
       },
     }),
 
+    // Gets a single property from this track's album, providing it as the same
+    // property name prefixed with '#album.' (by default). If the track's album
+    // isn't available, and earlyExitIfNotFound hasn't been set, the property
+    // will be provided as null.
+    withAlbumProperty: (property, {
+      to = '#album.' + property,
+      earlyExitIfNotFound = false,
+    } = {}) =>
+      Thing.composite.from(`Track.composite.withAlbumProperty`, [
+        Track.composite.withAlbum({earlyExitIfNotFound}),
+
+        {
+          flags: {expose: true, compose: true},
+          expose: {
+            dependencies: ['#album'],
+            options: {property},
+            mapContinuation: {to},
+
+            compute: ({
+              '#album': album,
+              '#options': {property},
+            }, continuation) =>
+              (album
+                ? continuation.raise({to: album[property]})
+                : continuation.raise({to: null})),
+          },
+        },
+      ]),
+
     // Gets the listed properties from this track's album, providing them as
     // dependencies (by default) with '#album.' prefixed before each property
     // name. If the track's album isn't available, and earlyExitIfNotFound