« 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/track.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things/track.js')
-rw-r--r--src/data/things/track.js332
1 files changed, 332 insertions, 0 deletions
diff --git a/src/data/things/track.js b/src/data/things/track.js
new file mode 100644
index 0000000..d2930ff
--- /dev/null
+++ b/src/data/things/track.js
@@ -0,0 +1,332 @@
+import Thing from './thing.js';
+
+import {inspect} from 'util';
+import {color} from '../../util/cli.js';
+
+import find from '../../util/find.js';
+
+export class Track extends Thing {
+  static [Thing.referenceType] = 'track';
+
+  static [Thing.getPropertyDescriptors] = ({
+    Album,
+    ArtTag,
+    Artist,
+    Flash,
+
+    validators: {
+      isBoolean,
+      isDate,
+      isDuration,
+      isFileExtension,
+    },
+  }) => ({
+    // Update & expose
+
+    name: Thing.common.name('Unnamed Track'),
+    directory: Thing.common.directory(),
+
+    duration: {
+      flags: {update: true, expose: true},
+      update: {validate: isDuration},
+    },
+
+    urls: Thing.common.urls(),
+    dateFirstReleased: Thing.common.simpleDate(),
+
+    hasURLs: Thing.common.flag(true),
+
+    artistContribsByRef: Thing.common.contribsByRef(),
+    contributorContribsByRef: Thing.common.contribsByRef(),
+    coverArtistContribsByRef: Thing.common.contribsByRef(),
+
+    referencedTracksByRef: Thing.common.referenceList(Track),
+    sampledTracksByRef: Thing.common.referenceList(Track),
+    artTagsByRef: Thing.common.referenceList(ArtTag),
+
+    hasCoverArt: {
+      flags: {update: true, expose: true},
+
+      update: {validate: isBoolean},
+
+      expose: {
+        dependencies: ['albumData', 'coverArtistContribsByRef'],
+        transform: (hasCoverArt, {
+          albumData,
+          coverArtistContribsByRef,
+          [Track.instance]: track,
+        }) =>
+          Track.hasCoverArt(
+            track,
+            albumData,
+            coverArtistContribsByRef,
+            hasCoverArt
+          ),
+      },
+    },
+
+    coverArtFileExtension: {
+      flags: {update: true, expose: true},
+
+      update: {validate: isFileExtension},
+
+      expose: {
+        dependencies: ['albumData', 'coverArtistContribsByRef'],
+        transform: (coverArtFileExtension, {
+          albumData,
+          coverArtistContribsByRef,
+          hasCoverArt,
+          [Track.instance]: track,
+        }) =>
+          coverArtFileExtension ??
+          (Track.hasCoverArt(
+            track,
+            albumData,
+            coverArtistContribsByRef,
+            hasCoverArt
+          )
+            ? Track.findAlbum(track, albumData)?.trackCoverArtFileExtension
+            : Track.findAlbum(track, albumData)?.coverArtFileExtension) ??
+          'jpg',
+      },
+    },
+
+    // Previously known as: (track).aka
+    originalReleaseTrackByRef: Thing.common.singleReference(Track),
+
+    dataSourceAlbumByRef: Thing.common.singleReference(Album),
+
+    commentary: Thing.common.commentary(),
+    lyrics: Thing.common.simpleString(),
+    additionalFiles: Thing.common.additionalFiles(),
+
+    // Update only
+
+    albumData: Thing.common.wikiData(Album),
+    artistData: Thing.common.wikiData(Artist),
+    artTagData: Thing.common.wikiData(ArtTag),
+    flashData: Thing.common.wikiData(Flash),
+    trackData: Thing.common.wikiData(Track),
+
+    // Expose only
+
+    commentatorArtists: Thing.common.commentatorArtists(),
+
+    album: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['albumData'],
+        compute: ({[Track.instance]: track, albumData}) =>
+          albumData?.find((album) => album.tracks.includes(track)) ?? null,
+      },
+    },
+
+    // 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
+    // (i.e. a track may dynamically be moved from one album to another, at
+    // which point dataSourceAlbum refers to where it was originally from, and is
+    // not generally relevant information). It's also not guaranteed that
+    // dataSourceAlbum is available (depending on the Track creator to optionally
+    // provide dataSourceAlbumByRef).
+    dataSourceAlbum: Thing.common.dynamicThingFromSingleReference(
+      'dataSourceAlbumByRef',
+      'albumData',
+      find.album
+    ),
+
+    date: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['albumData', 'dateFirstReleased'],
+        compute: ({albumData, dateFirstReleased, [Track.instance]: track}) =>
+          dateFirstReleased ?? Track.findAlbum(track, albumData)?.date ?? null,
+      },
+    },
+
+    color: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['albumData'],
+
+        compute: ({albumData, [Track.instance]: track}) =>
+          Track.findAlbum(track, albumData)?.trackGroups.find((tg) =>
+            tg.tracks.includes(track)
+          )?.color ?? null,
+      },
+    },
+
+    coverArtDate: {
+      flags: {update: true, expose: true},
+
+      update: {validate: isDate},
+
+      expose: {
+        dependencies: ['albumData', 'dateFirstReleased'],
+        transform: (coverArtDate, {
+          albumData,
+          dateFirstReleased,
+          [Track.instance]: track,
+        }) =>
+          coverArtDate ??
+          dateFirstReleased ??
+          Track.findAlbum(track, albumData)?.trackArtDate ??
+          Track.findAlbum(track, albumData)?.date ??
+          null,
+      },
+    },
+
+    originalReleaseTrack: Thing.common.dynamicThingFromSingleReference(
+      'originalReleaseTrackByRef',
+      'trackData',
+      find.track
+    ),
+
+    otherReleases: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['originalReleaseTrackByRef', 'trackData'],
+
+        compute: ({
+          originalReleaseTrackByRef: t1origRef,
+          trackData,
+          [Track.instance]: t1,
+        }) => {
+          if (!trackData) {
+            return [];
+          }
+
+          const t1orig = find.track(t1origRef, trackData);
+
+          return [
+            t1orig,
+            ...trackData.filter((t2) => {
+              const {originalReleaseTrack: t2orig} = t2;
+              return t2 !== t1 && t2orig && (t2orig === t1orig || t2orig === t1);
+            }),
+          ].filter(Boolean);
+        },
+      },
+    },
+
+    // Previously known as: (track).artists
+    artistContribs: Thing.common.dynamicInheritContribs(
+      'artistContribsByRef',
+      'artistContribsByRef',
+      'albumData',
+      Track.findAlbum
+    ),
+
+    // Previously known as: (track).contributors
+    contributorContribs: Thing.common.dynamicContribs('contributorContribsByRef'),
+
+    // Previously known as: (track).coverArtists
+    coverArtistContribs: Thing.common.dynamicInheritContribs(
+      'coverArtistContribsByRef',
+      'trackCoverArtistContribsByRef',
+      'albumData',
+      Track.findAlbum
+    ),
+
+    // Previously known as: (track).references
+    referencedTracks: Thing.common.dynamicThingsFromReferenceList(
+      'referencedTracksByRef',
+      'trackData',
+      find.track
+    ),
+
+    sampledTracks: Thing.common.dynamicThingsFromReferenceList(
+      'sampledTracksByRef',
+      'trackData',
+      find.track
+    ),
+
+    // Specifically exclude re-releases from this list - while it's useful to
+    // get from a re-release to the tracks it references, re-releases aren't
+    // generally relevant from the perspective of the tracks being referenced.
+    // Filtering them from data here hides them from the corresponding field
+    // on the site (obviously), and has the bonus of not counting them when
+    // counting the number of times a track has been referenced, for use in
+    // the "Tracks - by Times Referenced" listing page (or other data
+    // processing).
+    referencedByTracks: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['trackData'],
+
+        compute: ({trackData, [Track.instance]: track}) =>
+          trackData
+            ? trackData
+                .filter((t) => !t.originalReleaseTrack)
+                .filter((t) => t.referencedTracks?.includes(track))
+            : [],
+      },
+    },
+
+    // For the same reasoning, exclude re-releases from sampled tracks too.
+    sampledByTracks: {
+      flags: {expose: true},
+
+      expose: {
+        dependencies: ['trackData'],
+
+        compute: ({trackData, [Track.instance]: track}) =>
+          trackData
+            ? trackData
+                .filter((t) => !t.originalReleaseTrack)
+                .filter((t) => t.sampledTracks?.includes(track))
+            : [],
+      },
+    },
+
+    // Previously known as: (track).flashes
+    featuredInFlashes: Thing.common.reverseReferenceList(
+      'flashData',
+      'featuredTracks'
+    ),
+
+    artTags: Thing.common.dynamicThingsFromReferenceList(
+      'artTagsByRef',
+      'artTagData',
+      find.artTag
+    ),
+  });
+
+  // This is a quick utility function for now, since the same code is reused in
+  // several places. Ideally it wouldn't be - we'd just reuse the `album`
+  // property - but support for that hasn't been coded yet :P
+  static findAlbum = (track, albumData) =>
+    albumData?.find((album) => album.tracks.includes(track));
+
+  // Another reused utility function. This one's logic is a bit more complicated.
+  static hasCoverArt = (
+    track,
+    albumData,
+    coverArtistContribsByRef,
+    hasCoverArt
+  ) => (
+    hasCoverArt ??
+    (coverArtistContribsByRef?.length > 0 || null) ??
+    Track.findAlbum(track, albumData)?.hasTrackArt ??
+    true
+  );
+
+  [inspect.custom]() {
+    const base = Thing.prototype[inspect.custom].apply(this);
+
+    const {album, dataSourceAlbum} = this;
+    const albumName = album ? album.name : dataSourceAlbum?.name;
+    const albumIndex =
+      albumName &&
+      (album ? album.tracks.indexOf(this) : dataSourceAlbum.tracks.indexOf(this));
+    const trackNum = albumIndex === -1 ? '#?' : `#${albumIndex + 1}`;
+
+    return albumName
+      ? base + ` (${color.yellow(trackNum)} in ${color.green(albumName)})`
+      : base;
+  }
+}