diff options
Diffstat (limited to 'src/data/composite/things/track')
19 files changed, 978 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 00000000..f47086d9 --- /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 00000000..e789e736 --- /dev/null +++ b/src/data/composite/things/track/index.js @@ -0,0 +1,17 @@ +export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt.js'; +export {default as inheritContributionListFromMainRelease} from './inheritContributionListFromMainRelease.js'; +export {default as inheritFromMainRelease} from './inheritFromMainRelease.js'; +export {default as withAllReleases} from './withAllReleases.js'; +export {default as withAlwaysReferenceByDirectory} from './withAlwaysReferenceByDirectory.js'; +export {default as withContainingTrackSection} from './withContainingTrackSection.js'; +export {default as withCoverArtistContribs} from './withCoverArtistContribs.js'; +export {default as withDate} from './withDate.js'; +export {default as withDirectorySuffix} from './withDirectorySuffix.js'; +export {default as withHasUniqueCoverArt} from './withHasUniqueCoverArt.js'; +export {default as withMainRelease} from './withMainRelease.js'; +export {default as withOtherReleases} from './withOtherReleases.js'; +export {default as withPropertyFromAlbum} from './withPropertyFromAlbum.js'; +export {default as withPropertyFromMainRelease} from './withPropertyFromMainRelease.js'; +export {default as withSuffixDirectoryFromAlbum} from './withSuffixDirectoryFromAlbum.js'; +export {default as withTrackArtDate} from './withTrackArtDate.js'; +export {default as withTrackNumber} from './withTrackNumber.js'; diff --git a/src/data/composite/things/track/inheritContributionListFromMainRelease.js b/src/data/composite/things/track/inheritContributionListFromMainRelease.js new file mode 100644 index 00000000..89252feb --- /dev/null +++ b/src/data/composite/things/track/inheritContributionListFromMainRelease.js @@ -0,0 +1,44 @@ +// Like inheritFromMainRelease, but tuned for contributions. +// Recontextualizes contributions for this track. + +import {input, templateCompositeFrom} from '#composite'; + +import {exposeDependency, raiseOutputWithoutDependency} + from '#composite/control-flow'; +import {withRecontextualizedContributionList, withRedatedContributionList} + from '#composite/wiki-data'; + +import withDate from './withDate.js'; +import withPropertyFromMainRelease + from './withPropertyFromMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `inheritContributionListFromMainRelease`, + + steps: () => [ + withPropertyFromMainRelease({ + property: input.thisProperty(), + notFoundValue: input.value([]), + }), + + raiseOutputWithoutDependency({ + dependency: '#isSecondaryRelease', + mode: input.value('falsy'), + }), + + withRecontextualizedContributionList({ + list: '#mainReleaseValue', + }), + + withDate(), + + withRedatedContributionList({ + list: '#mainReleaseValue', + date: '#date', + }), + + exposeDependency({ + dependency: '#mainReleaseValue', + }), + ], +}); diff --git a/src/data/composite/things/track/inheritFromMainRelease.js b/src/data/composite/things/track/inheritFromMainRelease.js new file mode 100644 index 00000000..b1cbb65e --- /dev/null +++ b/src/data/composite/things/track/inheritFromMainRelease.js @@ -0,0 +1,41 @@ +// Early exits with the value for the same property as specified on the +// main release, if this track is a secondary release, and otherwise continues +// without providing any further dependencies. +// +// Like withMainRelease, this will early exit (with notFoundValue) if the +// main release is specified by reference and that reference doesn't +// resolve to anything. + +import {input, templateCompositeFrom} from '#composite'; + +import {exposeDependency, raiseOutputWithoutDependency} + from '#composite/control-flow'; + +import withPropertyFromMainRelease + from './withPropertyFromMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `inheritFromMainRelease`, + + inputs: { + notFoundValue: input({ + defaultValue: null, + }), + }, + + steps: () => [ + withPropertyFromMainRelease({ + property: input.thisProperty(), + notFoundValue: input('notFoundValue'), + }), + + raiseOutputWithoutDependency({ + dependency: '#isSecondaryRelease', + mode: input.value('falsy'), + }), + + exposeDependency({ + dependency: '#mainReleaseValue', + }), + ], +}); diff --git a/src/data/composite/things/track/trackAdditionalNameList.js b/src/data/composite/things/track/trackAdditionalNameList.js new file mode 100644 index 00000000..65a2263d --- /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/withAllReleases.js b/src/data/composite/things/track/withAllReleases.js new file mode 100644 index 00000000..b93bf753 --- /dev/null +++ b/src/data/composite/things/track/withAllReleases.js @@ -0,0 +1,47 @@ +// Gets all releases of the current track. All items of the outputs are +// distinct Track objects; one track is the main release; all else are +// secondary releases of that main release; and one item, which may be +// the main release or one of the secondary releases, is the current +// track. The results are sorted by date, and it is possible that the +// main release is not actually the earliest/first. + +import {input, templateCompositeFrom} from '#composite'; +import {sortByDate} from '#sort'; + +import {exitWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +import withMainRelease from './withMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `withAllReleases`, + + outputs: ['#allReleases'], + + steps: () => [ + withMainRelease({ + selfIfMain: input.value(true), + notFoundValue: input.value([]), + }), + + // We don't talk about bruno no no + // Yes, this can perform a normal access equivalent to + // `this.secondaryReleases` from within a data composition. + // Oooooooooooooooooooooooooooooooooooooooooooooooo + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('secondaryReleases'), + }), + + { + dependencies: ['#mainRelease', '#mainRelease.secondaryReleases'], + compute: (continuation, { + ['#mainRelease']: mainRelease, + ['#mainRelease.secondaryReleases']: secondaryReleases, + }) => continuation({ + ['#allReleases']: + sortByDate([mainRelease, ...secondaryReleases]), + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js new file mode 100644 index 00000000..60faeaf4 --- /dev/null +++ b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js @@ -0,0 +1,97 @@ +// 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 {withPropertyFromObject} from '#composite/data'; +import {withResolvedReference} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +import { + exitWithoutDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withAlwaysReferenceByDirectory`, + + outputs: ['#alwaysReferenceByDirectory'], + + steps: () => [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromAlbum({ + property: input.value('alwaysReferenceTracksByDirectory'), + }), + + // Falsy mode means this exposes true if the album's property is true, + // but continues if the property is false (which is also the default). + exposeDependencyOrContinue({ + dependency: '#album.alwaysReferenceTracksByDirectory', + mode: input.value('falsy'), + }), + + // 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 mainReleaseTrack. + + exitWithoutDependency({ + dependency: 'trackData', + mode: input.value('empty'), + value: input.value(false), + }), + + exitWithoutDependency({ + dependency: 'mainReleaseTrack', + value: input.value(false), + }), + + // It's necessary to use the custom trackMainReleasesOnly 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.trackMainReleasesOnly excludes tracks which have + // an mainReleaseTrack 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: 'mainReleaseTrack', + data: 'trackData', + find: input.value(find.trackMainReleasesOnly), + }).outputs({ + '#resolvedReference': '#mainRelease', + }), + + exitWithoutDependency({ + dependency: '#mainRelease', + value: input.value(false), + }), + + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('name'), + }), + + { + dependencies: ['name', '#mainRelease.name'], + compute: (continuation, { + name, + ['#mainRelease.name']: mainReleaseName, + }) => continuation({ + ['#alwaysReferenceByDirectory']: + name === mainReleaseName, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withContainingTrackSection.js b/src/data/composite/things/track/withContainingTrackSection.js new file mode 100644 index 00000000..3d4d081e --- /dev/null +++ b/src/data/composite/things/track/withContainingTrackSection.js @@ -0,0 +1,20 @@ +// Gets the track section containing this track from its album's track list. + +import {templateCompositeFrom} from '#composite'; + +import {withUniqueReferencingThing} from '#composite/wiki-data'; +import {soupyReverse} from '#composite/wiki-properties'; + +export default templateCompositeFrom({ + annotation: `withContainingTrackSection`, + + outputs: ['#trackSection'], + + steps: () => [ + withUniqueReferencingThing({ + reverse: soupyReverse.input('trackSectionsWhichInclude'), + }).outputs({ + ['#uniqueReferencingThing']: '#trackSection', + }), + ], +}); diff --git a/src/data/composite/things/track/withCoverArtistContribs.js b/src/data/composite/things/track/withCoverArtistContribs.js new file mode 100644 index 00000000..9057cfeb --- /dev/null +++ b/src/data/composite/things/track/withCoverArtistContribs.js @@ -0,0 +1,73 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isContributionList} from '#validators'; + +import {exposeDependencyOrContinue} from '#composite/control-flow'; + +import { + withRecontextualizedContributionList, + withRedatedContributionList, + withResolvedContribs, +} from '#composite/wiki-data'; + +import exitWithoutUniqueCoverArt from './exitWithoutUniqueCoverArt.js'; +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withTrackArtDate from './withTrackArtDate.js'; + +export default templateCompositeFrom({ + annotation: `withCoverArtistContribs`, + + inputs: { + from: input({ + defaultDependency: 'coverArtistContribs', + validate: isContributionList, + acceptsNull: true, + }), + }, + + outputs: ['#coverArtistContribs'], + + steps: () => [ + exitWithoutUniqueCoverArt({ + value: input.value([]), + }), + + withTrackArtDate(), + + withResolvedContribs({ + from: input('from'), + thingProperty: input.value('coverArtistContribs'), + artistProperty: input.value('trackCoverArtistContributions'), + date: '#trackArtDate', + }).outputs({ + '#resolvedContribs': '#coverArtistContribs', + }), + + exposeDependencyOrContinue({ + dependency: '#coverArtistContribs', + mode: input.value('empty'), + }), + + withPropertyFromAlbum({ + property: input.value('trackCoverArtistContribs'), + }), + + withRecontextualizedContributionList({ + list: '#album.trackCoverArtistContribs', + artistProperty: input.value('trackCoverArtistContributions'), + }), + + withRedatedContributionList({ + list: '#album.trackCoverArtistContribs', + date: '#trackArtDate', + }), + + { + dependencies: ['#album.trackCoverArtistContribs'], + compute: (continuation, { + ['#album.trackCoverArtistContribs']: coverArtistContribs, + }) => continuation({ + ['#coverArtistContribs']: coverArtistContribs, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withDate.js b/src/data/composite/things/track/withDate.js new file mode 100644 index 00000000..b5a770e9 --- /dev/null +++ b/src/data/composite/things/track/withDate.js @@ -0,0 +1,34 @@ +// Gets the track's own date. This is either its dateFirstReleased property +// or, if unset, the album's date. + +import {input, templateCompositeFrom} from '#composite'; + +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withDate`, + + outputs: ['#date'], + + steps: () => [ + { + dependencies: ['dateFirstReleased'], + compute: (continuation, {dateFirstReleased}) => + (dateFirstReleased + ? continuation.raiseOutput({'#date': dateFirstReleased}) + : continuation()), + }, + + withPropertyFromAlbum({ + property: input.value('date'), + }), + + { + dependencies: ['#album.date'], + compute: (continuation, {['#album.date']: albumDate}) => + (albumDate + ? continuation.raiseOutput({'#date': albumDate}) + : continuation.raiseOutput({'#date': null})), + }, + ], +}) diff --git a/src/data/composite/things/track/withDirectorySuffix.js b/src/data/composite/things/track/withDirectorySuffix.js new file mode 100644 index 00000000..c063e158 --- /dev/null +++ b/src/data/composite/things/track/withDirectorySuffix.js @@ -0,0 +1,36 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withSuffixDirectoryFromAlbum from './withSuffixDirectoryFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withDirectorySuffix`, + + outputs: ['#directorySuffix'], + + steps: () => [ + withSuffixDirectoryFromAlbum(), + + raiseOutputWithoutDependency({ + dependency: '#suffixDirectoryFromAlbum', + mode: input.value('falsy'), + output: input.value({['#directorySuffix']: null}), + }), + + withPropertyFromAlbum({ + property: input.value('directorySuffix'), + }), + + { + dependencies: ['#album.directorySuffix'], + compute: (continuation, { + ['#album.directorySuffix']: directorySuffix, + }) => continuation({ + ['#directorySuffix']: + directorySuffix, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withHasUniqueCoverArt.js b/src/data/composite/things/track/withHasUniqueCoverArt.js new file mode 100644 index 00000000..85d3b92a --- /dev/null +++ b/src/data/composite/things/track/withHasUniqueCoverArt.js @@ -0,0 +1,108 @@ +// 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.) +// +// withHasUniqueCoverArt is based only around the presence of *specified* +// cover artist contributions, not whether the references to artists on those +// contributions actually resolve to anything. It completely evades interacting +// with find/replace. + +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {fillMissingListItems, withFlattenedList, withPropertyFromList} + from '#composite/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()), + }, + + withResultOfAvailabilityCheck({ + from: 'coverArtistContribs', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability'], + compute: (continuation, { + ['#availability']: availability, + }) => + (availability + ? continuation.raiseOutput({ + ['#hasUniqueCoverArt']: true, + }) + : continuation()), + }, + + withPropertyFromAlbum({ + property: input.value('trackCoverArtistContribs'), + internal: input.value(true), + }), + + withResultOfAvailabilityCheck({ + from: '#album.trackCoverArtistContribs', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability'], + compute: (continuation, { + ['#availability']: availability, + }) => + (availability + ? continuation.raiseOutput({ + ['#hasUniqueCoverArt']: true, + }) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'trackArtworks', + mode: input.value('empty'), + output: input.value({'#hasUniqueCoverArt': false}), + }), + + withPropertyFromList({ + list: 'trackArtworks', + property: input.value('artistContribs'), + internal: input.value(true), + }), + + // Since we're getting the update value for each artwork's artistContribs, + // it may not be set at all, and in that case won't be exposing as []. + fillMissingListItems({ + list: '#trackArtworks.artistContribs', + fill: input.value([]), + }), + + withFlattenedList({ + list: '#trackArtworks.artistContribs', + }), + + withResultOfAvailabilityCheck({ + from: '#flattenedList', + mode: input.value('empty'), + }).outputs({ + '#availability': '#hasUniqueCoverArt', + }), + ], +}); diff --git a/src/data/composite/things/track/withMainRelease.js b/src/data/composite/things/track/withMainRelease.js new file mode 100644 index 00000000..3a91edae --- /dev/null +++ b/src/data/composite/things/track/withMainRelease.js @@ -0,0 +1,70 @@ +// Just includes the main release of this track as a dependency. +// If this track isn't a secondary release, then it'll provide null, unless +// the {selfIfMain} option is set, in which case it'll provide this track +// itself. This will early exit (with notFoundValue) if the main release +// is specified by reference and that reference doesn't resolve to anything. + +import {input, templateCompositeFrom} from '#composite'; + +import {exitWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {withResolvedReference} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +export default templateCompositeFrom({ + annotation: `withMainRelease`, + + inputs: { + selfIfMain: input({type: 'boolean', defaultValue: false}), + notFoundValue: input({defaultValue: null}), + }, + + outputs: ['#mainRelease'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: 'mainReleaseTrack', + }), + + { + dependencies: [ + input.myself(), + input('selfIfMain'), + '#availability', + ], + + compute: (continuation, { + [input.myself()]: track, + [input('selfIfMain')]: selfIfMain, + '#availability': availability, + }) => + (availability + ? continuation() + : continuation.raiseOutput({ + ['#mainRelease']: + (selfIfMain ? track : null), + })), + }, + + withResolvedReference({ + ref: 'mainReleaseTrack', + find: soupyFind.input('track'), + }), + + exitWithoutDependency({ + dependency: '#resolvedReference', + value: input('notFoundValue'), + }), + + { + dependencies: ['#resolvedReference'], + + compute: (continuation, { + ['#resolvedReference']: resolvedReference, + }) => + continuation({ + ['#mainRelease']: resolvedReference, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withOtherReleases.js b/src/data/composite/things/track/withOtherReleases.js new file mode 100644 index 00000000..0639742f --- /dev/null +++ b/src/data/composite/things/track/withOtherReleases.js @@ -0,0 +1,30 @@ +// Gets all releases of the current track *except* this track itself; +// in other words, all other releases of the current track. + +import {input, templateCompositeFrom} from '#composite'; + +import {exitWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +import withAllReleases from './withAllReleases.js'; + +export default templateCompositeFrom({ + annotation: `withOtherReleases`, + + outputs: ['#otherReleases'], + + steps: () => [ + withAllReleases(), + + { + dependencies: [input.myself(), '#allReleases'], + compute: (continuation, { + [input.myself()]: thisTrack, + ['#allReleases']: allReleases, + }) => continuation({ + ['#otherReleases']: + allReleases.filter(track => track !== thisTrack), + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withPropertyFromAlbum.js b/src/data/composite/things/track/withPropertyFromAlbum.js new file mode 100644 index 00000000..a203c2e7 --- /dev/null +++ b/src/data/composite/things/track/withPropertyFromAlbum.js @@ -0,0 +1,48 @@ +// 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 {withPropertyFromObject} from '#composite/data'; + +export default templateCompositeFrom({ + annotation: `withPropertyFromAlbum`, + + inputs: { + property: input.staticValue({type: 'string'}), + internal: input({type: 'boolean', defaultValue: false}), + }, + + outputs: ({ + [input.staticValue('property')]: property, + }) => ['#album.' + property], + + steps: () => [ + // XXX: This is a ridiculous hack considering `defaultValue` above. + // If we were certain what was up, we'd just get around to fixing it LOL + { + dependencies: [input('internal')], + compute: (continuation, { + [input('internal')]: internal, + }) => continuation({ + ['#internal']: internal ?? false, + }), + }, + + withPropertyFromObject({ + object: 'album', + property: input('property'), + internal: '#internal', + }), + + { + dependencies: ['#value', input.staticValue('property')], + compute: (continuation, { + ['#value']: value, + [input.staticValue('property')]: property, + }) => continuation({ + ['#album.' + property]: value, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withPropertyFromMainRelease.js b/src/data/composite/things/track/withPropertyFromMainRelease.js new file mode 100644 index 00000000..393a4c63 --- /dev/null +++ b/src/data/composite/things/track/withPropertyFromMainRelease.js @@ -0,0 +1,86 @@ +// Provides a value inherited from the main release, if applicable, and a +// flag indicating if this track is a secondary release or not. +// +// Like withMainRelease, this will early exit (with notFoundValue) if the +// main release is specified by reference and that reference doesn't +// resolve to anything. + +import {input, templateCompositeFrom} from '#composite'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +import withMainRelease from './withMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `inheritFromMainRelease`, + + inputs: { + property: input({type: 'string'}), + + notFoundValue: input({ + defaultValue: null, + }), + }, + + outputs: ({ + [input.staticValue('property')]: property, + }) => + ['#isSecondaryRelease'].concat( + (property + ? ['#mainRelease.' + property] + : ['#mainReleaseValue'])), + + steps: () => [ + withMainRelease({ + notFoundValue: input('notFoundValue'), + }), + + withResultOfAvailabilityCheck({ + from: '#mainRelease', + }), + + { + dependencies: [ + '#availability', + input.staticValue('property'), + ], + + compute: (continuation, { + ['#availability']: availability, + [input.staticValue('property')]: property, + }) => + (availability + ? continuation() + : continuation.raiseOutput( + Object.assign( + {'#isSecondaryRelease': false}, + (property + ? {['#mainRelease.' + property]: null} + : {'#mainReleaseValue': null})))), + }, + + withPropertyFromObject({ + object: '#mainRelease', + property: input('property'), + }), + + { + dependencies: [ + '#value', + input.staticValue('property'), + ], + + compute: (continuation, { + ['#value']: value, + [input.staticValue('property')]: property, + }) => + continuation.raiseOutput( + Object.assign( + {'#isSecondaryRelease': true}, + (property + ? {['#mainRelease.' + property]: value} + : {'#mainReleaseValue': value}))), + }, + ], +}); diff --git a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js new file mode 100644 index 00000000..7159a3f4 --- /dev/null +++ b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js @@ -0,0 +1,53 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; + +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withSuffixDirectoryFromAlbum`, + + inputs: { + flagValue: input({ + defaultDependency: 'suffixDirectoryFromAlbum', + acceptsNull: true, + }), + }, + + outputs: ['#suffixDirectoryFromAlbum'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: 'suffixDirectoryFromAlbum', + }), + + { + dependencies: [ + '#availability', + 'suffixDirectoryFromAlbum' + ], + + compute: (continuation, { + ['#availability']: availability, + ['suffixDirectoryFromAlbum']: flagValue, + }) => + (availability + ? continuation.raiseOutput({['#suffixDirectoryFromAlbum']: flagValue}) + : continuation()), + }, + + withPropertyFromAlbum({ + property: input.value('suffixTrackDirectories'), + }), + + { + dependencies: ['#album.suffixTrackDirectories'], + compute: (continuation, { + ['#album.suffixTrackDirectories']: suffixTrackDirectories, + }) => continuation({ + ['#suffixDirectoryFromAlbum']: + suffixTrackDirectories, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withTrackArtDate.js b/src/data/composite/things/track/withTrackArtDate.js new file mode 100644 index 00000000..9b7b61c7 --- /dev/null +++ b/src/data/composite/things/track/withTrackArtDate.js @@ -0,0 +1,60 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isDate} from '#validators'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withDate from './withDate.js'; +import withHasUniqueCoverArt from './withHasUniqueCoverArt.js'; +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withTrackArtDate`, + + inputs: { + from: input({ + validate: isDate, + defaultDependency: 'coverArtDate', + acceptsNull: true, + }), + }, + + outputs: ['#trackArtDate'], + + steps: () => [ + withHasUniqueCoverArt(), + + raiseOutputWithoutDependency({ + dependency: '#hasUniqueCoverArt', + mode: input.value('falsy'), + output: input.value({'#trackArtDate': null}), + }), + + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: from, + }) => + (from + ? continuation.raiseOutput({'#trackArtDate': from}) + : continuation()), + }, + + withPropertyFromAlbum({ + property: input.value('trackArtDate'), + }), + + { + dependencies: ['#album.trackArtDate'], + compute: (continuation, { + ['#album.trackArtDate']: albumTrackArtDate, + }) => + (albumTrackArtDate + ? continuation.raiseOutput({'#trackArtDate': albumTrackArtDate}) + : continuation()), + }, + + withDate().outputs({ + '#date': '#trackArtDate', + }), + ], +}); diff --git a/src/data/composite/things/track/withTrackNumber.js b/src/data/composite/things/track/withTrackNumber.js new file mode 100644 index 00000000..61428e8c --- /dev/null +++ b/src/data/composite/things/track/withTrackNumber.js @@ -0,0 +1,50 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withIndexInList, withPropertiesFromObject} from '#composite/data'; + +import withContainingTrackSection from './withContainingTrackSection.js'; + +export default templateCompositeFrom({ + annotation: `withTrackNumber`, + + outputs: ['#trackNumber'], + + steps: () => [ + withContainingTrackSection(), + + // Zero is the fallback, not one, but in most albums the first track + // (and its intended output by this composition) will be one. + raiseOutputWithoutDependency({ + dependency: '#trackSection', + output: input.value({'#trackNumber': 0}), + }), + + withPropertiesFromObject({ + object: '#trackSection', + properties: input.value(['tracks', 'startCountingFrom']), + }), + + withIndexInList({ + list: '#trackSection.tracks', + item: input.myself(), + }), + + raiseOutputWithoutDependency({ + dependency: '#index', + output: input.value({'#trackNumber': 0}), + }), + + { + dependencies: ['#trackSection.startCountingFrom', '#index'], + compute: (continuation, { + ['#trackSection.startCountingFrom']: startCountingFrom, + ['#index']: index, + }) => continuation({ + ['#trackNumber']: + startCountingFrom + + index, + }), + }, + ], +}); |