diff options
Diffstat (limited to 'src/data/things/album.js')
| -rw-r--r-- | src/data/things/album.js | 897 |
1 files changed, 527 insertions, 370 deletions
diff --git a/src/data/things/album.js b/src/data/things/album.js index 7a7b387d..31d94ef1 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -3,14 +3,22 @@ export const DATA_ALBUM_DIRECTORY = 'album'; import * as path from 'node:path'; import {inspect} from 'node:util'; -import CacheableObject from '#cacheable-object'; import {colors} from '#cli'; -import {input} from '#composite'; +import {input, V} from '#composite'; import {traverse} from '#node-utils'; import {sortAlbumsTracksChronologically, sortChronologically} from '#sort'; -import {accumulateSum, empty} from '#sugar'; +import {empty} from '#sugar'; import Thing from '#thing'; -import {isColor, isDate, isDirectory, isNumber} from '#validators'; + +import { + is, + isBoolean, + isColor, + isContributionList, + isDate, + isDirectory, + isNumber, +} from '#validators'; import { parseAdditionalFiles, @@ -25,29 +33,40 @@ import { parseWallpaperParts, } from '#yaml'; -import {exitWithoutDependency, exposeDependency, exposeUpdateValueOrContinue} - from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - -import {exitWithoutContribs, withDirectory, withCoverArtDate} +import {withRecontextualizedContributionList, withResolvedContribs} from '#composite/wiki-data'; import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import { + withFlattenedList, + withLengthOfList, + withNearbyItemFromList, + withPropertyFromList, + withPropertyFromObject, +} from '#composite/data'; + +import { color, commentatorArtists, constitutibleArtwork, constitutibleArtworkList, contentString, - contribsPresent, contributionList, dimensions, directory, fileExtension, flag, + hasArtwork, name, referencedArtworkList, referenceList, - reverseReferenceList, simpleDate, simpleString, soupyFind, @@ -59,12 +78,15 @@ import { wikiData, } from '#composite/wiki-properties'; -import {withHasCoverArt, withTracks} from '#composite/things/album'; -import {withAlbum, withContinueCountingFrom, withStartCountingFrom} - from '#composite/things/track-section'; - export class Album extends Thing { static [Thing.referenceType] = 'album'; + static [Thing.wikiData] = 'albumData'; + + static [Thing.constitutibleProperties] = [ + 'coverArtworks', + 'wallpaperArtwork', + 'bannerArtwork', + ]; static [Thing.getPropertyDescriptors] = ({ AdditionalFile, @@ -74,13 +96,16 @@ export class Album extends Thing { CommentaryEntry, CreditingSourcesEntry, Group, - Track, TrackSection, WikiInfo, }) => ({ - // Update & expose + // > Update & expose - Internal relationships + + trackSections: thingList(V(TrackSection)), - name: name('Unnamed Album'), + // > Update & expose - Identifying metadata + + name: name(V('Unnamed Album')), directory: directory(), directorySuffix: [ @@ -88,156 +113,143 @@ export class Album extends Thing { validate: input.value(isDirectory), }), - withDirectory(), - - exposeDependency({ - dependency: '#directory', - }), + exposeDependency('directory'), ], - alwaysReferenceByDirectory: flag(false), - alwaysReferenceTracksByDirectory: flag(false), - suffixTrackDirectories: flag(false), + alwaysReferenceByDirectory: flag(V(false)), + alwaysReferenceTracksByDirectory: flag(V(false)), + suffixTrackDirectories: flag(V(false)), - color: color(), - urls: urls(), + style: [ + exposeUpdateValueOrContinue({ + validate: input.value(is(...[ + 'album', + 'single', + ])), + }), - additionalNames: thingList({ - class: input.value(AdditionalName), - }), + exposeConstant(V('album')), + ], bandcampAlbumIdentifier: simpleString(), bandcampArtworkIdentifier: simpleString(), + additionalNames: thingList(V(AdditionalName)), + date: simpleDate(), - trackArtDate: simpleDate(), dateAddedToWiki: simpleDate(), - coverArtDate: [ - withCoverArtDate({ - from: input.updateValue({ - validate: isDate, - }), + // > Update & expose - Credits and contributors + + artistContribs: contributionList({ + artistProperty: input.value('albumArtistContributions'), + }), + + trackArtistText: contentString(), + + trackArtistContribs: [ + withResolvedContribs({ + from: input.updateValue({validate: isContributionList}), + thingProperty: input.thisProperty(), + artistProperty: input.value('albumTrackArtistContributions'), + }).outputs({ + '#resolvedContribs': '#trackArtistContribs', }), - exposeDependency({dependency: '#coverArtDate'}), - ], + exposeDependencyOrContinue('#trackArtistContribs', V('empty')), - coverArtFileExtension: [ - exitWithoutContribs({contribs: 'coverArtistContribs'}), - fileExtension('jpg'), + withRecontextualizedContributionList('artistContribs', { + artistProperty: input.value('albumTrackArtistContributions'), + }), + + exposeDependency('#artistContribs'), ], - trackCoverArtFileExtension: fileExtension('jpg'), + // > Update & expose - General configuration - wallpaperFileExtension: [ - exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), - fileExtension('jpg'), - ], + countTracksInArtistTotals: flag(V(true)), - bannerFileExtension: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - fileExtension('jpg'), - ], + showAlbumInTracksWithoutArtists: flag(V(false)), - wallpaperStyle: [ - exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), - simpleString(), - ], + hasTrackNumbers: flag(V(true)), + isListedOnHomepage: flag(V(true)), + isListedInGalleries: flag(V(true)), - wallpaperParts: [ - exitWithoutContribs({ - contribs: 'wallpaperArtistContribs', + hideDuration: flag(V(false)), + + // > Update & expose - General metadata + + color: color(), + + urls: urls(), + + // > Update & expose - Artworks + + coverArtworks: [ + exitWithoutDependency('hasCoverArt', { value: input.value([]), + mode: input.value('falsy'), }), - wallpaperParts(), + constitutibleArtworkList.fromYAMLFieldSpec + .call(this, 'Cover Artwork'), ], - bannerStyle: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - simpleString(), - ], + coverArtistContribs: contributionList({ + date: 'coverArtDate', + artistProperty: input.value('albumCoverArtistContributions'), + }), - coverArtDimensions: [ - exitWithoutContribs({contribs: 'coverArtistContribs'}), - dimensions(), - ], + coverArtDate: [ + exitWithoutDependency('hasCoverArt', { + value: input.value(null), + mode: input.value('falsy'), + }), - trackDimensions: dimensions(), + exposeUpdateValueOrContinue({ + validate: input.value(isDate), + }), - bannerDimensions: [ - exitWithoutContribs({contribs: 'bannerArtistContribs'}), - dimensions(), + exposeDependency('date'), ], - wallpaperArtwork: [ - exitWithoutDependency({ - dependency: 'wallpaperArtistContribs', - mode: input.value('empty'), + coverArtFileExtension: [ + exitWithoutDependency('hasCoverArt', { value: input.value(null), + mode: input.value('falsy'), }), - constitutibleArtwork.fromYAMLFieldSpec - .call(this, 'Wallpaper Artwork'), + fileExtension(V('jpg')), ], - bannerArtwork: [ - exitWithoutDependency({ - dependency: 'bannerArtistContribs', - mode: input.value('empty'), + coverArtDimensions: [ + exitWithoutDependency('hasCoverArt', { value: input.value(null), + mode: input.value('falsy'), }), - constitutibleArtwork.fromYAMLFieldSpec - .call(this, 'Banner Artwork'), + dimensions(), ], - coverArtworks: [ - withHasCoverArt(), - - exitWithoutDependency({ - dependency: '#hasCoverArt', - mode: input.value('falsy'), + artTags: [ + exitWithoutDependency('hasCoverArt', { value: input.value([]), + mode: input.value('falsy'), }), - constitutibleArtworkList.fromYAMLFieldSpec - .call(this, 'Cover Artwork'), + referenceList({ + class: input.value(ArtTag), + find: soupyFind.input('artTag'), + }), ], - hasTrackNumbers: flag(true), - isListedOnHomepage: flag(true), - isListedInGalleries: flag(true), - - commentary: thingList({ - class: input.value(CommentaryEntry), - }), - - creditSources: thingList({ - class: input.value(CreditingSourcesEntry), - }), - - additionalFiles: thingList({ - class: input.value(AdditionalFile), - }), - - trackSections: thingList({ - class: input.value(TrackSection), - }), - - artistContribs: contributionList({ - date: 'date', - artistProperty: input.value('albumArtistContributions'), - }), - - coverArtistContribs: [ - withCoverArtDate(), - - contributionList({ - date: '#coverArtDate', - artistProperty: input.value('albumCoverArtistContributions'), + referencedArtworks: [ + exitWithoutDependency('hasCoverArt', { + value: input.value([]), + mode: input.value('falsy'), }), + + referencedArtworkList(), ], trackCoverArtistContribs: contributionList({ @@ -250,80 +262,150 @@ export class Album extends Thing { artistProperty: input.value('trackCoverArtistContributions'), }), - wallpaperArtistContribs: [ - withCoverArtDate(), + trackArtDate: simpleDate(), + + trackCoverArtFileExtension: fileExtension(V('jpg')), - contributionList({ - date: '#coverArtDate', - artistProperty: input.value('albumWallpaperArtistContributions'), + trackDimensions: dimensions(), + + wallpaperArtwork: [ + exitWithoutDependency('hasWallpaperArt', { + value: input.value(null), + mode: input.value('falsy'), }), + + constitutibleArtwork.fromYAMLFieldSpec + .call(this, 'Wallpaper Artwork'), ], - bannerArtistContribs: [ - withCoverArtDate(), + wallpaperArtistContribs: contributionList({ + date: 'coverArtDate', + artistProperty: input.value('albumWallpaperArtistContributions'), + }), - contributionList({ - date: '#coverArtDate', - artistProperty: input.value('albumBannerArtistContributions'), + wallpaperFileExtension: [ + exitWithoutDependency('hasWallpaperArt', { + value: input.value(null), + mode: input.value('falsy'), }), + + fileExtension(V('jpg')), ], - groups: referenceList({ - class: input.value(Group), - find: soupyFind.input('group'), - }), + wallpaperStyle: [ + exitWithoutDependency('hasWallpaperArt', { + value: input.value(null), + mode: input.value('falsy'), + }), - artTags: [ - exitWithoutContribs({ - contribs: 'coverArtistContribs', + simpleString(), + ], + + wallpaperParts: [ + exitWithoutDependency('hasWallpaperArt', { value: input.value([]), + mode: input.value('falsy'), }), - referenceList({ - class: input.value(ArtTag), - find: soupyFind.input('artTag'), + wallpaperParts(), + ], + + bannerArtwork: [ + exitWithoutDependency('hasBannerArt', { + value: input.value(null), + mode: input.value('falsy'), }), + + constitutibleArtwork.fromYAMLFieldSpec + .call(this, 'Banner Artwork'), ], - referencedArtworks: [ - exitWithoutContribs({ - contribs: 'coverArtistContribs', - value: input.value([]), + bannerArtistContribs: contributionList({ + date: 'coverArtDate', + artistProperty: input.value('albumBannerArtistContributions'), + }), + + bannerFileExtension: [ + exitWithoutDependency('hasBannerArt', { + value: input.value(null), + mode: input.value('falsy'), }), - referencedArtworkList(), + fileExtension(V('jpg')), ], - // Update only + bannerDimensions: [ + exitWithoutDependency('hasBannerArt', { + value: input.value(null), + mode: input.value('falsy'), + }), + + dimensions(), + ], + + bannerStyle: [ + exitWithoutDependency('hasBannerArt', { + value: input.value(null), + mode: input.value('falsy'), + }), + + simpleString(), + ], + + // > Update & expose - Groups + + groups: referenceList({ + class: input.value(Group), + find: soupyFind.input('group'), + }), + + // > Update & expose - Content entries + + commentary: thingList(V(CommentaryEntry)), + creditingSources: thingList(V(CreditingSourcesEntry)), + + // > Update & expose - Additional files + + additionalFiles: thingList(V(AdditionalFile)), + + // > Update only find: soupyFind(), reverse: soupyReverse(), // used for referencedArtworkList (mixedFind) - artworkData: wikiData({ - class: input.value(Artwork), - }), + artworkData: wikiData(V(Artwork)), // used for withMatchingContributionPresets (indirectly by Contribution) - wikiInfo: thing({ - class: input.value(WikiInfo), - }), + wikiInfo: thing(V(WikiInfo)), - // Expose only + // > Expose only + + isAlbum: exposeConstant(V(true)), commentatorArtists: commentatorArtists(), - hasCoverArt: [ - withHasCoverArt(), - exposeDependency({dependency: '#hasCoverArt'}), - ], + hasCoverArt: hasArtwork({ + contribs: '_coverArtistContribs', + artworks: '_coverArtworks', + }), + + hasWallpaperArt: hasArtwork({ + contribs: '_wallpaperArtistContribs', + artwork: '_wallpaperArtwork', + }), - hasWallpaperArt: contribsPresent({contribs: 'wallpaperArtistContribs'}), - hasBannerArt: contribsPresent({contribs: 'bannerArtistContribs'}), + hasBannerArt: hasArtwork({ + contribs: '_bannerArtistContribs', + artwork: '_bannerArtwork', + }), tracks: [ - withTracks(), - exposeDependency({dependency: '#tracks'}), + exitWithoutDependency('trackSections', V([])), + + withPropertyFromList('trackSections', V('tracks')), + withFlattenedList('#trackSections.tracks'), + exposeDependency('#flattenedList'), ], }); @@ -378,8 +460,22 @@ export class Album extends Thing { bindTo: 'albumData', getMatchableNames: album => - (album.alwaysReferenceByDirectory - ? [] + (album.alwaysReferenceByDirectory + ? [] + : [album.name]), + }, + + albumSinglesOnly: { + referencing: ['album'], + + bindTo: 'albumData', + + incldue: album => + album.style === 'single', + + getMatchableNames: album => + (album.alwaysReferenceByDirectory + ? [] : [album.name]), }, @@ -396,8 +492,8 @@ export class Album extends Thing { album.hasCoverArt, getMatchableNames: album => - (album.alwaysReferenceByDirectory - ? [] + (album.alwaysReferenceByDirectory + ? [] : [album.name]), }, @@ -428,20 +524,6 @@ export class Album extends Thing { }; static [Thing.reverseSpecs] = { - albumsWhoseTracksInclude: { - bindTo: 'albumData', - - referencing: album => [album], - referenced: album => album.tracks, - }, - - albumsWhoseTrackSectionsInclude: { - bindTo: 'albumData', - - referencing: album => [album], - referenced: album => album.trackSections, - }, - albumsWhoseArtworksFeature: { bindTo: 'albumData', @@ -459,6 +541,9 @@ export class Album extends Thing { albumArtistContributionsBy: soupyReverse.contributionsBy('albumData', 'artistContribs'), + albumTrackArtistContributionsBy: + soupyReverse.contributionsBy('albumData', 'trackArtistContribs'), + albumCoverArtistContributionsBy: soupyReverse.artworkContributionsBy('albumData', 'coverArtworks'), @@ -478,21 +563,15 @@ export class Album extends Thing { static [Thing.yamlDocumentSpec] = { fields: { - 'Album': {property: 'name'}, + // Identifying metadata + 'Album': {property: 'name'}, 'Directory': {property: 'directory'}, 'Directory Suffix': {property: 'directorySuffix'}, 'Suffix Track Directories': {property: 'suffixTrackDirectories'}, - 'Always Reference By Directory': {property: 'alwaysReferenceByDirectory'}, - 'Always Reference Tracks By Directory': { - property: 'alwaysReferenceTracksByDirectory', - }, - - 'Additional Names': { - property: 'additionalNames', - transform: parseAdditionalNames, - }, + 'Always Reference Tracks By Directory': {property: 'alwaysReferenceTracksByDirectory'}, + 'Style': {property: 'style'}, 'Bandcamp Album ID': { property: 'bandcampAlbumIdentifier', @@ -504,18 +583,61 @@ export class Album extends Thing { transform: String, }, + 'Additional Names': { + property: 'additionalNames', + transform: parseAdditionalNames, + }, + 'Date': { property: 'date', transform: parseDate, }, - 'Color': {property: 'color'}, - 'URLs': {property: 'urls'}, + 'Date Added': { + property: 'dateAddedToWiki', + transform: parseDate, + }, + + // Credits and contributors + + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, + + 'Track Artist Text': { + property: 'trackArtistText', + }, + + 'Track Artists': { + property: 'trackArtistContribs', + transform: parseContributors, + }, + + // General configuration + + 'Count Tracks In Artist Totals': {property: 'countTracksInArtistTotals'}, + + 'Show Album In Tracks Without Artists': { + property: 'showAlbumInTracksWithoutArtists', + }, 'Has Track Numbers': {property: 'hasTrackNumbers'}, 'Listed on Homepage': {property: 'isListedOnHomepage'}, 'Listed in Galleries': {property: 'isListedInGalleries'}, + 'Hide Duration': {property: 'hideDuration'}, + + // General metadata + + 'Color': {property: 'color'}, + + 'URLs': {property: 'urls'}, + + // Artworks + // (Note - this YAML section is deliberately ordered differently + // than the corresponding property descriptors.) + 'Cover Artwork': { property: 'coverArtworks', transform: @@ -559,27 +681,29 @@ export class Album extends Thing { }), }, + 'Cover Artists': { + property: 'coverArtistContribs', + transform: parseContributors, + }, + 'Cover Art Date': { property: 'coverArtDate', transform: parseDate, }, - 'Default Track Cover Art Date': { - property: 'trackArtDate', - transform: parseDate, + 'Cover Art Dimensions': { + property: 'coverArtDimensions', + transform: parseDimensions, }, - 'Date Added': { - property: 'dateAddedToWiki', - transform: parseDate, + 'Default Track Cover Artists': { + property: 'trackCoverArtistContribs', + transform: parseContributors, }, - 'Cover Art File Extension': {property: 'coverArtFileExtension'}, - 'Track Art File Extension': {property: 'trackCoverArtFileExtension'}, - - 'Cover Art Dimensions': { - property: 'coverArtDimensions', - transform: parseDimensions, + 'Default Track Cover Art Date': { + property: 'trackArtDate', + transform: parseDate, }, 'Default Track Dimensions': { @@ -593,7 +717,6 @@ export class Album extends Thing { }, 'Wallpaper Style': {property: 'wallpaperStyle'}, - 'Wallpaper File Extension': {property: 'wallpaperFileExtension'}, 'Wallpaper Parts': { property: 'wallpaperParts', @@ -605,58 +728,70 @@ export class Album extends Thing { transform: parseContributors, }, - 'Banner Style': {property: 'bannerStyle'}, - 'Banner File Extension': {property: 'bannerFileExtension'}, - 'Banner Dimensions': { property: 'bannerDimensions', transform: parseDimensions, }, - 'Commentary': { - property: 'commentary', - transform: parseCommentary, - }, + 'Banner Style': {property: 'bannerStyle'}, - 'Credit Sources': { - property: 'creditSources', - transform: parseCreditingSources, - }, + 'Cover Art File Extension': {property: 'coverArtFileExtension'}, + 'Track Art File Extension': {property: 'trackCoverArtFileExtension'}, + 'Wallpaper File Extension': {property: 'wallpaperFileExtension'}, + 'Banner File Extension': {property: 'bannerFileExtension'}, - 'Additional Files': { - property: 'additionalFiles', - transform: parseAdditionalFiles, - }, + 'Art Tags': {property: 'artTags'}, 'Referenced Artworks': { property: 'referencedArtworks', transform: parseAnnotatedReferences, }, - 'Franchises': {ignore: true}, + // Groups - 'Artists': { - property: 'artistContribs', - transform: parseContributors, + 'Groups': {property: 'groups'}, + + // Content entries + + 'Commentary': { + property: 'commentary', + transform: parseCommentary, }, - 'Cover Artists': { - property: 'coverArtistContribs', - transform: parseContributors, + 'Crediting Sources': { + property: 'creditingSources', + transform: parseCreditingSources, }, - 'Default Track Cover Artists': { - property: 'trackCoverArtistContribs', - transform: parseContributors, + // Additional files + + 'Additional Files': { + property: 'additionalFiles', + transform: parseAdditionalFiles, }, - 'Groups': {property: 'groups'}, - 'Art Tags': {property: 'artTags'}, + // Shenanigans + 'Franchises': {ignore: true}, 'Review Points': {ignore: true}, }, invalidFieldCombinations: [ + {message: `Move commentary on singles to the track`, fields: [ + ['Style', 'single'], + 'Commentary', + ]}, + + {message: `Move crediting sources on singles to the track`, fields: [ + ['Style', 'single'], + 'Crediting Sources', + ]}, + + {message: `Move additional names on singles to the track`, fields: [ + ['Style', 'single'], + 'Additional Names', + ]}, + {message: `Specify one wallpaper style or multiple wallpaper parts, not both`, fields: [ 'Wallpaper Parts', 'Wallpaper Style', @@ -688,100 +823,48 @@ export class Album extends Thing { ? TrackSection : Track), - save(results) { - const albumData = []; - const trackSectionData = []; - const trackData = []; - - const artworkData = []; - const commentaryData = []; - const creditingSourceData = []; - const lyricsData = []; - - for (const {header: album, entries} of results) { - const trackSections = []; - - let currentTrackSection = new TrackSection(); - let currentTrackSectionTracks = []; - - Object.assign(currentTrackSection, { - name: `Default Track Section`, - isDefaultTrackSection: true, - }); - - const albumRef = Thing.getReference(album); - - const closeCurrentTrackSection = () => { - if ( - currentTrackSection.isDefaultTrackSection && - empty(currentTrackSectionTracks) - ) { - return; - } - - currentTrackSection.tracks = - currentTrackSectionTracks; - - trackSections.push(currentTrackSection); - trackSectionData.push(currentTrackSection); - }; - - for (const entry of entries) { - if (entry instanceof TrackSection) { - closeCurrentTrackSection(); - currentTrackSection = entry; - currentTrackSectionTracks = []; - continue; - } - - currentTrackSectionTracks.push(entry); - trackData.push(entry); - - // Set the track's album before accessing its list of artworks. - // The existence of its artwork objects may depend on access to - // its album's 'Default Track Cover Artists'. - entry.album = album; - - artworkData.push(...entry.trackArtworks); - commentaryData.push(...entry.commentary); - creditingSourceData.push(...entry.creditSources); - - // TODO: As exposed, Track.lyrics tries to inherit from the main - // release, which is impossible before the data's been linked. - // We just use the update value here. But it's icky! - lyricsData.push(...CacheableObject.getUpdateValue(entry, 'lyrics') ?? []); - } - - closeCurrentTrackSection(); + connect({header: album, entries}) { + const trackSections = []; - albumData.push(album); + let currentTrackSection = new TrackSection(); + let currentTrackSectionTracks = []; - artworkData.push(...album.coverArtworks); + Object.assign(currentTrackSection, { + name: `Default Track Section`, + isDefaultTrackSection: true, + }); - if (album.bannerArtwork) { - artworkData.push(album.bannerArtwork); + const closeCurrentTrackSection = () => { + if ( + currentTrackSection.isDefaultTrackSection && + empty(currentTrackSectionTracks) + ) { + return; } - if (album.wallpaperArtwork) { - artworkData.push(album.wallpaperArtwork); + currentTrackSection.tracks = currentTrackSectionTracks; + currentTrackSection.album = album; + + trackSections.push(currentTrackSection); + }; + + for (const entry of entries) { + if (entry instanceof TrackSection) { + closeCurrentTrackSection(); + currentTrackSection = entry; + currentTrackSectionTracks = []; + continue; } - commentaryData.push(...album.commentary); - creditingSourceData.push(...album.creditSources); + entry.album = album; + entry.trackSection = currentTrackSection; - album.trackSections = trackSections; + currentTrackSectionTracks.push(entry); } - return { - albumData, - trackSectionData, - trackData, + closeCurrentTrackSection(); - artworkData, - commentaryData, - creditingSourceData, - lyricsData, - }; + album.trackSections = trackSections; }, sort({albumData, trackData}) { @@ -835,56 +918,120 @@ export class Album extends Thing { artwork.fileExtension, ]; } + + // As of writing, albums don't even have a `duration` property... + // so this function will never be called... but the message stands... + countOwnContributionInDurationTotals(_contrib) { + return false; + } } export class TrackSection extends Thing { static [Thing.friendlyName] = `Track Section`; static [Thing.referenceType] = `track-section`; + static [Thing.wikiData] = 'trackSectionData'; - static [Thing.getPropertyDescriptors] = ({Album, Track}) => ({ + static [Thing.getPropertyDescriptors] = ({Track}) => ({ // Update & expose - name: name('Unnamed Track Section'), + album: thing(V(Album)), + + name: name(V('Unnamed Track Section')), unqualifiedDirectory: directory(), + directorySuffix: [ + exposeUpdateValueOrContinue({ + validate: input.value(isDirectory), + }), + + withPropertyFromObject({ + object: 'album', + property: input.value('directorySuffix'), + }), + + exposeDependency({dependency: '#album.directorySuffix'}), + ], + + suffixTrackDirectories: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromObject({ + object: 'album', + property: input.value('suffixTrackDirectories'), + }), + + exposeDependency({dependency: '#album.suffixTrackDirectories'}), + ], + color: [ exposeUpdateValueOrContinue({ validate: input.value(isColor), }), - withAlbum(), - withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('color'), }), exposeDependency({dependency: '#album.color'}), ], + hasTrackNumbers: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromObject('album', V('hasTrackNumbers')), + exposeDependency('#album.hasTrackNumbers'), + ], + startCountingFrom: [ - withStartCountingFrom({ - from: input.updateValue({validate: isNumber}), + exposeUpdateValueOrContinue({ + validate: input.value(isNumber), + }), + + withPropertyFromObject('album', V('hasTrackNumbers')), + exitWithoutDependency('#album.hasTrackNumbers', V(1), V('falsy')), + + withPropertyFromObject('album', V('trackSections')), + + withNearbyItemFromList({ + list: '#album.trackSections', + item: input.myself(), + offset: input.value(-1), + }).outputs({ + '#nearbyItem': '#previousTrackSection', }), - exposeDependency({dependency: '#startCountingFrom'}), + exitWithoutDependency('#previousTrackSection', V(1)), + + withPropertyFromObject('#previousTrackSection', V('continueCountingFrom')), + exposeDependency('#previousTrackSection.continueCountingFrom'), ], dateOriginallyReleased: simpleDate(), - isDefaultTrackSection: flag(false), + countTracksInArtistTotals: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), - description: contentString(), + withPropertyFromObject({ + object: 'album', + property: input.value('countTracksInArtistTotals'), + }), - album: [ - withAlbum(), - exposeDependency({dependency: '#album'}), + exposeDependency({dependency: '#album.countTracksInArtistTotals'}), ], - tracks: thingList({ - class: input.value(Track), - }), + isDefaultTrackSection: flag(V(false)), + + description: contentString(), + + tracks: thingList(V(Track)), // Update only @@ -892,38 +1039,51 @@ export class TrackSection extends Thing { // Expose only - directory: [ - withAlbum(), + isTrackSection: [ + exposeConstant({ + value: input.value(true), + }), + ], + directory: [ exitWithoutDependency({ - dependency: '#album', + dependency: 'album', }), withPropertyFromObject({ - object: '#album', + object: 'album', property: input.value('directory'), }), - withDirectory({ - directory: 'unqualifiedDirectory', - }).outputs({ - '#directory': '#unqualifiedDirectory', - }), - { - dependencies: ['#album.directory', '#unqualifiedDirectory'], + dependencies: ['#album.directory', 'unqualifiedDirectory'], compute: ({ ['#album.directory']: albumDirectory, - ['#unqualifiedDirectory']: unqualifiedDirectory, + ['unqualifiedDirectory']: unqualifiedDirectory, }) => albumDirectory + '/' + unqualifiedDirectory, }, ], continueCountingFrom: [ - withContinueCountingFrom(), + withPropertyFromObject('album', V('hasTrackNumbers')), + exitWithoutDependency('#album.hasTrackNumbers', V(null), V('falsy')), - exposeDependency({dependency: '#continueCountingFrom'}), + { + dependencies: ['hasTrackNumbers', 'startCountingFrom'], + compute: (continuation, {hasTrackNumbers, startCountingFrom}) => + (hasTrackNumbers + ? continuation() + : continuation.exit(startCountingFrom)), + }, + + withLengthOfList('tracks'), + + { + dependencies: ['startCountingFrom', '#tracks.length'], + compute: ({startCountingFrom, '#tracks.length': tracks}) => + startCountingFrom + tracks, + }, ], }); @@ -941,19 +1101,14 @@ export class TrackSection extends Thing { }, }; - static [Thing.reverseSpecs] = { - trackSectionsWhichInclude: { - bindTo: 'trackSectionData', - - referencing: trackSection => [trackSection], - referenced: trackSection => trackSection.tracks, - }, - }; - static [Thing.yamlDocumentSpec] = { fields: { 'Section': {property: 'name'}, + 'Directory Suffix': {property: 'directorySuffix'}, + 'Suffix Track Directories': {property: 'suffixTrackDirectories'}, + 'Color': {property: 'color'}, + 'Has Track Numbers': {property: 'hasTrackNumbers'}, 'Start Counting From': {property: 'startCountingFrom'}, 'Date Originally Released': { @@ -961,6 +1116,8 @@ export class TrackSection extends Thing { transform: parseDate, }, + 'Count Tracks In Artist Totals': {property: 'countTracksInArtistTotals'}, + 'Description': {property: 'description'}, }, }; @@ -970,11 +1127,13 @@ export class TrackSection extends Thing { parts.push(Thing.prototype[inspect.custom].apply(this)); - if (depth >= 0) { + if (depth >= 0) showAlbum: { let album = null; try { album = this.album; - } catch {} + } catch { + break showAlbum; + } let first = null; try { @@ -986,22 +1145,20 @@ export class TrackSection extends Thing { last = this.tracks.at(-1).trackNumber; } catch {} - if (album) { - const albumName = album.name; - const albumIndex = album.trackSections.indexOf(this); + const albumName = album.name; + const albumIndex = album.trackSections.indexOf(this); - const num = - (albumIndex === -1 - ? 'indeterminate position' - : `#${albumIndex + 1}`); + const num = + (albumIndex === -1 + ? 'indeterminate position' + : `#${albumIndex + 1}`); - const range = - (albumIndex >= 0 && first !== null && last !== null - ? `: ${first}-${last}` - : ''); + const range = + (albumIndex >= 0 && first !== null && last !== null + ? `: ${first}-${last}` + : ''); - parts.push(` (${colors.yellow(num + range)} in ${colors.green(albumName)})`); - } + parts.push(` (${colors.yellow(num + range)} in ${colors.green(`"${albumName}"`)})`); } return parts.join(''); |