« 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/composite/things/track
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/composite/things/track')
-rw-r--r--src/data/composite/things/track/exitWithoutUniqueCoverArt.js26
-rw-r--r--src/data/composite/things/track/index.js11
-rw-r--r--src/data/composite/things/track/inferredAdditionalNameList.js67
-rw-r--r--src/data/composite/things/track/inheritFromOriginalRelease.js50
-rw-r--r--src/data/composite/things/track/sharedAdditionalNameList.js38
-rw-r--r--src/data/composite/things/track/trackAdditionalNameList.js38
-rw-r--r--src/data/composite/things/track/trackReverseReferenceList.js38
-rw-r--r--src/data/composite/things/track/withAlbum.js22
-rw-r--r--src/data/composite/things/track/withAlwaysReferenceByDirectory.js78
-rw-r--r--src/data/composite/things/track/withContainingTrackSection.js42
-rw-r--r--src/data/composite/things/track/withHasUniqueCoverArt.js61
-rw-r--r--src/data/composite/things/track/withOriginalRelease.js78
-rw-r--r--src/data/composite/things/track/withOtherReleases.js41
-rw-r--r--src/data/composite/things/track/withPropertyFromAlbum.js40
14 files changed, 630 insertions, 0 deletions
diff --git a/src/data/composite/things/track/exitWithoutUniqueCoverArt.js b/src/data/composite/things/track/exitWithoutUniqueCoverArt.js
new file mode 100644
index 0000000..f47086d
--- /dev/null
+++ b/src/data/composite/things/track/exitWithoutUniqueCoverArt.js
@@ -0,0 +1,26 @@
+// Shorthand for checking if the track has unique cover art and exposing a
+// fallback value if it isn't.
+
+import {input, templateCompositeFrom} from '#composite';
+
+import {exitWithoutDependency} from '#composite/control-flow';
+
+import withHasUniqueCoverArt from './withHasUniqueCoverArt.js';
+
+export default templateCompositeFrom({
+  annotation: `exitWithoutUniqueCoverArt`,
+
+  inputs: {
+    value: input({defaultValue: null}),
+  },
+
+  steps: () => [
+    withHasUniqueCoverArt(),
+
+    exitWithoutDependency({
+      dependency: '#hasUniqueCoverArt',
+      mode: input.value('falsy'),
+      value: input('value'),
+    }),
+  ],
+});
diff --git a/src/data/composite/things/track/index.js b/src/data/composite/things/track/index.js
new file mode 100644
index 0000000..cc723a2
--- /dev/null
+++ b/src/data/composite/things/track/index.js
@@ -0,0 +1,11 @@
+export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt.js';
+export {default as inferredAdditionalNameList} from './inferredAdditionalNameList.js';
+export {default as inheritFromOriginalRelease} from './inheritFromOriginalRelease.js';
+export {default as sharedAdditionalNameList} from './sharedAdditionalNameList.js';
+export {default as trackReverseReferenceList} from './trackReverseReferenceList.js';
+export {default as withAlbum} from './withAlbum.js';
+export {default as withAlwaysReferenceByDirectory} from './withAlwaysReferenceByDirectory.js';
+export {default as withContainingTrackSection} from './withContainingTrackSection.js';
+export {default as withHasUniqueCoverArt} from './withHasUniqueCoverArt.js';
+export {default as withOtherReleases} from './withOtherReleases.js';
+export {default as withPropertyFromAlbum} from './withPropertyFromAlbum.js';
diff --git a/src/data/composite/things/track/inferredAdditionalNameList.js b/src/data/composite/things/track/inferredAdditionalNameList.js
new file mode 100644
index 0000000..58e8d2a
--- /dev/null
+++ b/src/data/composite/things/track/inferredAdditionalNameList.js
@@ -0,0 +1,67 @@
+// Infers additional name entries from other releases that were titled
+// differently; the corresponding releases are stored in eacn entry's "from"
+// array, which will include multiple items, if more than one other release
+// shares the same name differing from this one's.
+
+import {input, templateCompositeFrom} from '#composite';
+import {chunkByProperties} from '#sugar';
+
+import {exitWithoutDependency} from '#composite/control-flow';
+import {withFilteredList, withPropertyFromList} from '#composite/data';
+import {withThingsSortedAlphabetically} from '#composite/wiki-data';
+
+import withOtherReleases from './withOtherReleases.js';
+
+export default templateCompositeFrom({
+  annotation: `inferredAdditionalNameList`,
+
+  compose: false,
+
+  steps: () => [
+    withOtherReleases(),
+
+    exitWithoutDependency({
+      dependency: '#otherReleases',
+      mode: input.value('empty'),
+      value: input.value([]),
+    }),
+
+    withPropertyFromList({
+      list: '#otherReleases',
+      property: input.value('name'),
+    }),
+
+    {
+      dependencies: ['#otherReleases.name', 'name'],
+      compute: (continuation, {
+        ['#otherReleases.name']: releaseNames,
+        ['name']: ownName,
+      }) => continuation({
+        ['#nameFilter']:
+          releaseNames.map(name => name !== ownName),
+      }),
+    },
+
+    withFilteredList({
+      list: '#otherReleases',
+      filter: '#nameFilter',
+    }).outputs({
+      '#filteredList': '#differentlyNamedReleases',
+    }),
+
+    withThingsSortedAlphabetically({
+      things: '#differentlyNamedReleases',
+    }).outputs({
+      '#sortedThings': '#differentlyNamedReleases',
+    }),
+
+    {
+      dependencies: ['#differentlyNamedReleases'],
+      compute: ({
+        ['#differentlyNamedReleases']: releases,
+      }) =>
+        chunkByProperties(releases, ['name'])
+          .map(({name, chunk}) => ({name, from: chunk})),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/inheritFromOriginalRelease.js b/src/data/composite/things/track/inheritFromOriginalRelease.js
new file mode 100644
index 0000000..27ed138
--- /dev/null
+++ b/src/data/composite/things/track/inheritFromOriginalRelease.js
@@ -0,0 +1,50 @@
+// Early exits with a value inherited from the original release, if
+// this track is a rerelease, and otherwise continues with no further
+// dependencies provided. If allowOverride is true, then the continuation
+// will also be called if the original release exposed the requested
+// property as null.
+//
+// Like withOriginalRelease, this will early exit (with notFoundValue) if the
+// original release is specified by reference and that reference doesn't
+// resolve to anything.
+
+import {input, templateCompositeFrom} from '#composite';
+
+import withOriginalRelease from './withOriginalRelease.js';
+
+export default templateCompositeFrom({
+  annotation: `inheritFromOriginalRelease`,
+
+  inputs: {
+    property: input({type: 'string'}),
+    allowOverride: input({type: 'boolean', defaultValue: false}),
+    notFoundValue: input({defaultValue: null}),
+  },
+
+  steps: () => [
+    withOriginalRelease({
+      notFoundValue: input('notFoundValue'),
+    }),
+
+    {
+      dependencies: [
+        '#originalRelease',
+        input('property'),
+        input('allowOverride'),
+      ],
+
+      compute: (continuation, {
+        ['#originalRelease']: originalRelease,
+        [input('property')]: originalProperty,
+        [input('allowOverride')]: allowOverride,
+      }) => {
+        if (!originalRelease) return continuation();
+
+        const value = originalRelease[originalProperty];
+        if (allowOverride && value === null) return continuation();
+
+        return continuation.exit(value);
+      },
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/sharedAdditionalNameList.js b/src/data/composite/things/track/sharedAdditionalNameList.js
new file mode 100644
index 0000000..1806ec8
--- /dev/null
+++ b/src/data/composite/things/track/sharedAdditionalNameList.js
@@ -0,0 +1,38 @@
+// Compiles additional names directly provided by other releases.
+
+import {input, templateCompositeFrom} from '#composite';
+
+import {exitWithoutDependency, exposeDependency}
+  from '#composite/control-flow';
+import {withFlattenedList, withPropertyFromList} from '#composite/data';
+
+import withOtherReleases from './withOtherReleases.js';
+
+export default templateCompositeFrom({
+  annotation: `sharedAdditionalNameList`,
+
+  compose: false,
+
+  steps: () => [
+    withOtherReleases(),
+
+    exitWithoutDependency({
+      dependency: '#otherReleases',
+      mode: input.value('empty'),
+      value: input.value([]),
+    }),
+
+    withPropertyFromList({
+      list: '#otherReleases',
+      property: input.value('additionalNames'),
+    }),
+
+    withFlattenedList({
+      list: '#otherReleases.additionalNames',
+    }),
+
+    exposeDependency({
+      dependency: '#flattenedList',
+    }),
+  ],
+});
diff --git a/src/data/composite/things/track/trackAdditionalNameList.js b/src/data/composite/things/track/trackAdditionalNameList.js
new file mode 100644
index 0000000..65a2263
--- /dev/null
+++ b/src/data/composite/things/track/trackAdditionalNameList.js
@@ -0,0 +1,38 @@
+// Compiles additional names from various sources.
+
+import {input, templateCompositeFrom} from '#composite';
+import {isAdditionalNameList} from '#validators';
+
+import withInferredAdditionalNames from './withInferredAdditionalNames.js';
+import withSharedAdditionalNames from './withSharedAdditionalNames.js';
+
+export default templateCompositeFrom({
+  annotation: `trackAdditionalNameList`,
+
+  compose: false,
+
+  update: {validate: isAdditionalNameList},
+
+  steps: () => [
+    withInferredAdditionalNames(),
+    withSharedAdditionalNames(),
+
+    {
+      dependencies: [
+        '#inferredAdditionalNames',
+        '#sharedAdditionalNames',
+        input.updateValue(),
+      ],
+
+      compute: ({
+        ['#inferredAdditionalNames']: inferredAdditionalNames,
+        ['#sharedAdditionalNames']: sharedAdditionalNames,
+        [input.updateValue()]: providedAdditionalNames,
+      }) => [
+        ...providedAdditionalNames ?? [],
+        ...sharedAdditionalNames,
+        ...inferredAdditionalNames,
+      ],
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/trackReverseReferenceList.js b/src/data/composite/things/track/trackReverseReferenceList.js
new file mode 100644
index 0000000..44940ae
--- /dev/null
+++ b/src/data/composite/things/track/trackReverseReferenceList.js
@@ -0,0 +1,38 @@
+// Like a normal reverse reference list ("objects which reference this object
+// under a specified property"), only excluding rereleases from the possible
+// outputs. While it's useful to travel from a rerelease to the tracks it
+// references, rereleases aren't generally relevant from the perspective of
+// the tracks *being* referenced. Apart from hiding rereleases from lists on
+// the site, it also excludes keeps them from relational data processing, such
+// as on the "Tracks - by Times Referenced" listing page.
+
+import {input, templateCompositeFrom} from '#composite';
+import {withReverseReferenceList} from '#composite/wiki-data';
+
+export default templateCompositeFrom({
+  annotation: `trackReverseReferenceList`,
+
+  compose: false,
+
+  inputs: {
+    list: input({type: 'string'}),
+  },
+
+  steps: () => [
+    withReverseReferenceList({
+      data: 'trackData',
+      list: input('list'),
+    }),
+
+    {
+      flags: {expose: true},
+      expose: {
+        dependencies: ['#reverseReferenceList'],
+        compute: ({
+          ['#reverseReferenceList']: reverseReferenceList,
+        }) =>
+          reverseReferenceList.filter(track => !track.originalReleaseTrack),
+      },
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withAlbum.js b/src/data/composite/things/track/withAlbum.js
new file mode 100644
index 0000000..03b840d
--- /dev/null
+++ b/src/data/composite/things/track/withAlbum.js
@@ -0,0 +1,22 @@
+// Gets the track's album. This will early exit if albumData is missing.
+// If there's no album whose list of tracks includes this track, the output
+// dependency will be null.
+
+import {input, templateCompositeFrom} from '#composite';
+
+import {withUniqueReferencingThing} from '#composite/wiki-data';
+
+export default templateCompositeFrom({
+  annotation: `withAlbum`,
+
+  outputs: ['#album'],
+
+  steps: () => [
+    withUniqueReferencingThing({
+      data: 'albumData',
+      list: input.value('tracks'),
+    }).outputs({
+      ['#uniqueReferencingThing']: '#album',
+    }),
+  ],
+});
diff --git a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js
new file mode 100644
index 0000000..fac8e21
--- /dev/null
+++ b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js
@@ -0,0 +1,78 @@
+// Controls how find.track works - it'll never be matched by a reference
+// just to the track's name, which means you don't have to always reference
+// some *other* (much more commonly referenced) track by directory instead
+// of more naturally by name.
+
+import {input, templateCompositeFrom} from '#composite';
+import find from '#find';
+import {isBoolean} from '#validators';
+
+import {exitWithoutDependency, exposeUpdateValueOrContinue}
+  from '#composite/control-flow';
+import {withPropertyFromObject} from '#composite/data';
+import {withResolvedReference} from '#composite/wiki-data';
+
+export default templateCompositeFrom({
+  annotation: `withAlwaysReferenceByDirectory`,
+
+  outputs: ['#alwaysReferenceByDirectory'],
+
+  steps: () => [
+    exposeUpdateValueOrContinue({
+      validate: input.value(isBoolean),
+    }),
+
+    // Remaining code is for defaulting to true if this track is a rerelease of
+    // another with the same name, so everything further depends on access to
+    // trackData as well as originalReleaseTrack.
+
+    exitWithoutDependency({
+      dependency: 'trackData',
+      mode: input.value('empty'),
+      value: input.value(false),
+    }),
+
+    exitWithoutDependency({
+      dependency: 'originalReleaseTrack',
+      value: input.value(false),
+    }),
+
+    // It's necessary to use the custom trackOriginalReleasesOnly find function
+    // here, so as to avoid recursion issues - the find.track() function depends
+    // on accessing each track's alwaysReferenceByDirectory, which means it'll
+    // hit *this track* - and thus this step - and end up recursing infinitely.
+    // By definition, find.trackOriginalReleasesOnly excludes tracks which have
+    // an originalReleaseTrack update value set, which means even though it does
+    // still access each of tracks' `alwaysReferenceByDirectory` property, it
+    // won't access that of *this* track - it will never proceed past the
+    // `exitWithoutDependency` step directly above, so there's no opportunity
+    // for recursion.
+    withResolvedReference({
+      ref: 'originalReleaseTrack',
+      data: 'trackData',
+      find: input.value(find.trackOriginalReleasesOnly),
+    }).outputs({
+      '#resolvedReference': '#originalRelease',
+    }),
+
+    exitWithoutDependency({
+      dependency: '#originalRelease',
+      value: input.value(false),
+    }),
+
+    withPropertyFromObject({
+      object: '#originalRelease',
+      property: input.value('name'),
+    }),
+
+    {
+      dependencies: ['name', '#originalRelease.name'],
+      compute: (continuation, {
+        name,
+        ['#originalRelease.name']: originalName,
+      }) => continuation({
+        ['#alwaysReferenceByDirectory']: name === originalName,
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withContainingTrackSection.js b/src/data/composite/things/track/withContainingTrackSection.js
new file mode 100644
index 0000000..eaac14d
--- /dev/null
+++ b/src/data/composite/things/track/withContainingTrackSection.js
@@ -0,0 +1,42 @@
+// Gets the track section containing this track from its album's track list.
+
+import {input, templateCompositeFrom} from '#composite';
+import {is} from '#validators';
+
+import {raiseOutputWithoutDependency} from '#composite/control-flow';
+
+import withPropertyFromAlbum from './withPropertyFromAlbum.js';
+
+export default templateCompositeFrom({
+  annotation: `withContainingTrackSection`,
+
+  outputs: ['#trackSection'],
+
+  steps: () => [
+    withPropertyFromAlbum({
+      property: input.value('trackSections'),
+    }),
+
+    raiseOutputWithoutDependency({
+      dependency: '#album.trackSections',
+      output: input.value({'#trackSection': null}),
+    }),
+
+    {
+      dependencies: [
+        input.myself(),
+        '#album.trackSections',
+      ],
+
+      compute: (continuation, {
+        [input.myself()]: track,
+        [input('notFoundMode')]: notFoundMode,
+        ['#album.trackSections']: trackSections,
+      }) => continuation({
+        ['#trackSection']:
+          trackSections.find(({tracks}) => tracks.includes(track))
+            ?? null,
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withHasUniqueCoverArt.js b/src/data/composite/things/track/withHasUniqueCoverArt.js
new file mode 100644
index 0000000..96078d5
--- /dev/null
+++ b/src/data/composite/things/track/withHasUniqueCoverArt.js
@@ -0,0 +1,61 @@
+// Whether or not the track has "unique" cover artwork - a cover which is
+// specifically associated with this track in particular, rather than with
+// the track's album as a whole. This is typically used to select between
+// displaying the track artwork and a fallback, such as the album artwork
+// or a placeholder. (This property is named hasUniqueCoverArt instead of
+// the usual hasCoverArt to emphasize that it does not inherit from the
+// album.)
+
+import {input, templateCompositeFrom} from '#composite';
+import {empty} from '#sugar';
+
+import {withResolvedContribs} from '#composite/wiki-data';
+
+import withPropertyFromAlbum from './withPropertyFromAlbum.js';
+
+export default templateCompositeFrom({
+  annotation: 'withHasUniqueCoverArt',
+
+  outputs: ['#hasUniqueCoverArt'],
+
+  steps: () => [
+    {
+      dependencies: ['disableUniqueCoverArt'],
+      compute: (continuation, {disableUniqueCoverArt}) =>
+        (disableUniqueCoverArt
+          ? continuation.raiseOutput({
+              ['#hasUniqueCoverArt']: false,
+            })
+          : continuation()),
+    },
+
+    withResolvedContribs({from: 'coverArtistContribs'}),
+
+    {
+      dependencies: ['#resolvedContribs'],
+      compute: (continuation, {
+        ['#resolvedContribs']: contribsFromTrack,
+      }) =>
+        (empty(contribsFromTrack)
+          ? continuation()
+          : continuation.raiseOutput({
+              ['#hasUniqueCoverArt']: true,
+            })),
+    },
+
+    withPropertyFromAlbum({
+      property: input.value('trackCoverArtistContribs'),
+    }),
+
+    {
+      dependencies: ['#album.trackCoverArtistContribs'],
+      compute: (continuation, {
+        ['#album.trackCoverArtistContribs']: contribsFromAlbum,
+      }) =>
+        continuation.raiseOutput({
+          ['#hasUniqueCoverArt']:
+            !empty(contribsFromAlbum),
+        }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withOriginalRelease.js b/src/data/composite/things/track/withOriginalRelease.js
new file mode 100644
index 0000000..c7f4965
--- /dev/null
+++ b/src/data/composite/things/track/withOriginalRelease.js
@@ -0,0 +1,78 @@
+// Just includes the original release of this track as a dependency.
+// If this track isn't a rerelease, then it'll provide null, unless the
+// {selfIfOriginal} option is set, in which case it'll provide this track
+// itself. This will early exit (with notFoundValue) if the original release
+// is specified by reference and that reference doesn't resolve to anything.
+
+import {input, templateCompositeFrom} from '#composite';
+import find from '#find';
+import {validateWikiData} from '#validators';
+
+import {exitWithoutDependency, withResultOfAvailabilityCheck}
+  from '#composite/control-flow';
+import {withResolvedReference} from '#composite/wiki-data';
+
+export default templateCompositeFrom({
+  annotation: `withOriginalRelease`,
+
+  inputs: {
+    selfIfOriginal: input({type: 'boolean', defaultValue: false}),
+
+    data: input({
+      validate: validateWikiData({referenceType: 'track'}),
+      defaultDependency: 'trackData',
+    }),
+
+    notFoundValue: input({defaultValue: null}),
+  },
+
+  outputs: ['#originalRelease'],
+
+  steps: () => [
+    withResultOfAvailabilityCheck({
+      from: 'originalReleaseTrack',
+    }),
+
+    {
+      dependencies: [
+        input.myself(),
+        input('selfIfOriginal'),
+        '#availability',
+      ],
+
+      compute: (continuation, {
+        [input.myself()]: track,
+        [input('selfIfOriginal')]: selfIfOriginal,
+        '#availability': availability,
+      }) =>
+        (availability
+          ? continuation()
+          : continuation.raiseOutput({
+              ['#originalRelease']:
+                (selfIfOriginal ? track : null),
+            })),
+    },
+
+    withResolvedReference({
+      ref: 'originalReleaseTrack',
+      data: input('data'),
+      find: input.value(find.track),
+    }),
+
+    exitWithoutDependency({
+      dependency: '#resolvedReference',
+      value: input('notFoundValue'),
+    }),
+
+    {
+      dependencies: ['#resolvedReference'],
+
+      compute: (continuation, {
+        ['#resolvedReference']: resolvedReference,
+      }) =>
+        continuation({
+          ['#originalRelease']: resolvedReference,
+        }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withOtherReleases.js b/src/data/composite/things/track/withOtherReleases.js
new file mode 100644
index 0000000..f8c1c3f
--- /dev/null
+++ b/src/data/composite/things/track/withOtherReleases.js
@@ -0,0 +1,41 @@
+import {input, templateCompositeFrom} from '#composite';
+
+import {exitWithoutDependency} from '#composite/control-flow';
+
+import withOriginalRelease from './withOriginalRelease.js';
+
+export default templateCompositeFrom({
+  annotation: `withOtherReleases`,
+
+  outputs: ['#otherReleases'],
+
+  steps: () => [
+    exitWithoutDependency({
+      dependency: 'trackData',
+      mode: input.value('empty'),
+    }),
+
+    withOriginalRelease({
+      selfIfOriginal: input.value(true),
+      notFoundValue: input.value([]),
+    }),
+
+    {
+      dependencies: [input.myself(), '#originalRelease', 'trackData'],
+      compute: (continuation, {
+        [input.myself()]: thisTrack,
+        ['#originalRelease']: originalRelease,
+        trackData,
+      }) => continuation({
+        ['#otherReleases']:
+          (originalRelease === thisTrack
+            ? []
+            : [originalRelease])
+            .concat(trackData.filter(track =>
+              track !== originalRelease &&
+              track !== thisTrack &&
+              track.originalReleaseTrack === originalRelease)),
+      }),
+    },
+  ],
+});
diff --git a/src/data/composite/things/track/withPropertyFromAlbum.js b/src/data/composite/things/track/withPropertyFromAlbum.js
new file mode 100644
index 0000000..d41390f
--- /dev/null
+++ b/src/data/composite/things/track/withPropertyFromAlbum.js
@@ -0,0 +1,40 @@
+// Gets a single property from this track's album, providing it as the same
+// property name prefixed with '#album.' (by default).
+
+import {input, templateCompositeFrom} from '#composite';
+import {is} from '#validators';
+
+import {withPropertyFromObject} from '#composite/data';
+
+import withAlbum from './withAlbum.js';
+
+export default templateCompositeFrom({
+  annotation: `withPropertyFromAlbum`,
+
+  inputs: {
+    property: input.staticValue({type: 'string'}),
+  },
+
+  outputs: ({
+    [input.staticValue('property')]: property,
+  }) => ['#album.' + property],
+
+  steps: () => [
+    withAlbum(),
+
+    withPropertyFromObject({
+      object: '#album',
+      property: input('property'),
+    }),
+
+    {
+      dependencies: ['#value', input.staticValue('property')],
+      compute: (continuation, {
+        ['#value']: value,
+        [input.staticValue('property')]: property,
+      }) => continuation({
+        ['#album.' + property]: value,
+      }),
+    },
+  ],
+});