diff options
Diffstat (limited to 'src/data/things/album.js')
-rw-r--r-- | src/data/things/album.js | 521 |
1 files changed, 356 insertions, 165 deletions
diff --git a/src/data/things/album.js b/src/data/things/album.js index 4890aaa..40cd463 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -1,134 +1,177 @@ -import Thing from './thing.js'; - -import find from '../../util/find.js'; +export const DATA_ALBUM_DIRECTORY = 'album'; + +import * as path from 'node:path'; + +import {input} from '#composite'; +import find from '#find'; +import {traverse} from '#node-utils'; +import {sortAlbumsTracksChronologically, sortChronologically} from '#sort'; +import {empty} from '#sugar'; +import Thing from '#thing'; +import {isDate} from '#validators'; +import {parseAdditionalFiles, parseContributors, parseDate, parseDimensions} + from '#yaml'; + +import {exposeDependency, exposeUpdateValueOrContinue} + from '#composite/control-flow'; +import {exitWithoutContribs} from '#composite/wiki-data'; + +import { + additionalFiles, + commentary, + color, + commentatorArtists, + contribsPresent, + contributionList, + dimensions, + directory, + fileExtension, + flag, + name, + referenceList, + simpleDate, + simpleString, + urls, + wikiData, +} from '#composite/wiki-properties'; + +import {withTracks, withTrackSections} from '#composite/things/album'; export class Album extends Thing { static [Thing.referenceType] = 'album'; - static [Thing.getPropertyDescriptors] = ({ - ArtTag, - Artist, - Group, - Track, - TrackGroup, - - validators: { - isDate, - isDimensions, - validateArrayItems, - validateInstanceOf, - }, - }) => ({ + static [Thing.getPropertyDescriptors] = ({ArtTag, Artist, Group, Track}) => ({ // Update & expose - name: Thing.common.name('Unnamed Album'), - color: Thing.common.color(), - directory: Thing.common.directory(), - urls: Thing.common.urls(), - - date: Thing.common.simpleDate(), - trackArtDate: Thing.common.simpleDate(), - dateAddedToWiki: Thing.common.simpleDate(), - - coverArtDate: { - flags: {update: true, expose: true}, - - update: {validate: isDate}, - - expose: { - dependencies: ['date'], - transform: (coverArtDate, {date}) => coverArtDate ?? date ?? null, - }, - }, - - artistContribsByRef: Thing.common.contribsByRef(), - coverArtistContribsByRef: Thing.common.contribsByRef(), - trackCoverArtistContribsByRef: Thing.common.contribsByRef(), - wallpaperArtistContribsByRef: Thing.common.contribsByRef(), - bannerArtistContribsByRef: Thing.common.contribsByRef(), - - groupsByRef: Thing.common.referenceList(Group), - artTagsByRef: Thing.common.referenceList(ArtTag), - - trackGroups: { - flags: {update: true, expose: true}, - - update: { - validate: validateArrayItems(validateInstanceOf(TrackGroup)), - }, - }, - - coverArtFileExtension: Thing.common.fileExtension('jpg'), - trackCoverArtFileExtension: Thing.common.fileExtension('jpg'), - - wallpaperStyle: Thing.common.simpleString(), - wallpaperFileExtension: Thing.common.fileExtension('jpg'), + name: name('Unnamed Album'), + color: color(), + directory: directory(), + urls: urls(), + + bandcampAlbumIdentifier: simpleString(), + bandcampArtworkIdentifier: simpleString(), + + date: simpleDate(), + trackArtDate: simpleDate(), + dateAddedToWiki: simpleDate(), + + coverArtDate: [ + exitWithoutContribs({contribs: 'coverArtistContribs'}), + + exposeUpdateValueOrContinue({ + validate: input.value(isDate), + }), + + exposeDependency({dependency: 'date'}), + ], + + coverArtFileExtension: [ + exitWithoutContribs({contribs: 'coverArtistContribs'}), + fileExtension('jpg'), + ], + + trackCoverArtFileExtension: fileExtension('jpg'), + + wallpaperFileExtension: [ + exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), + fileExtension('jpg'), + ], + + bannerFileExtension: [ + exitWithoutContribs({contribs: 'bannerArtistContribs'}), + fileExtension('jpg'), + ], + + wallpaperStyle: [ + exitWithoutContribs({contribs: 'wallpaperArtistContribs'}), + simpleString(), + ], + + bannerStyle: [ + exitWithoutContribs({contribs: 'bannerArtistContribs'}), + simpleString(), + ], + + coverArtDimensions: [ + exitWithoutContribs({contribs: 'coverArtistContribs'}), + dimensions(), + ], + + bannerDimensions: [ + exitWithoutContribs({contribs: 'bannerArtistContribs'}), + dimensions(), + ], + + hasTrackNumbers: flag(true), + isListedOnHomepage: flag(true), + isListedInGalleries: flag(true), + + commentary: commentary(), + additionalFiles: additionalFiles(), + + trackSections: [ + withTrackSections(), + exposeDependency({dependency: '#trackSections'}), + ], + + artistContribs: contributionList(), + coverArtistContribs: contributionList(), + trackCoverArtistContribs: contributionList(), + wallpaperArtistContribs: contributionList(), + bannerArtistContribs: contributionList(), + + groups: referenceList({ + class: input.value(Group), + find: input.value(find.group), + data: 'groupData', + }), + + artTags: [ + exitWithoutContribs({ + contribs: 'coverArtistContribs', + value: input.value([]), + }), + + referenceList({ + class: input.value(ArtTag), + find: input.value(find.artTag), + data: 'artTagData', + }), + ], - bannerStyle: Thing.common.simpleString(), - bannerFileExtension: Thing.common.fileExtension('jpg'), - bannerDimensions: { - flags: {update: true, expose: true}, - update: {validate: isDimensions}, - }, + // Update only - hasCoverArt: Thing.common.flag(true), - hasTrackArt: Thing.common.flag(true), - hasTrackNumbers: Thing.common.flag(true), - isMajorRelease: Thing.common.flag(false), - isListedOnHomepage: Thing.common.flag(true), + artistData: wikiData({ + class: input.value(Artist), + }), - commentary: Thing.common.commentary(), - additionalFiles: Thing.common.additionalFiles(), + artTagData: wikiData({ + class: input.value(ArtTag), + }), - // Update only + groupData: wikiData({ + class: input.value(Group), + }), - artistData: Thing.common.wikiData(Artist), - artTagData: Thing.common.wikiData(ArtTag), - groupData: Thing.common.wikiData(Group), - trackData: Thing.common.wikiData(Track), + // Only the tracks which belong to this album. + // Necessary for computing the track list, so provide this statically + // or keep it updated. + ownTrackData: wikiData({ + class: input.value(Track), + }), // Expose only - artistContribs: Thing.common.dynamicContribs('artistContribsByRef'), - coverArtistContribs: Thing.common.dynamicContribs('coverArtistContribsByRef'), - trackCoverArtistContribs: Thing.common.dynamicContribs( - 'trackCoverArtistContribsByRef' - ), - wallpaperArtistContribs: Thing.common.dynamicContribs( - 'wallpaperArtistContribsByRef' - ), - bannerArtistContribs: Thing.common.dynamicContribs( - 'bannerArtistContribsByRef' - ), - - commentatorArtists: Thing.common.commentatorArtists(), - - tracks: { - flags: {expose: true}, - - expose: { - dependencies: ['trackGroups', 'trackData'], - compute: ({trackGroups, trackData}) => - trackGroups && trackData - ? trackGroups - .flatMap((group) => group.tracksByRef ?? []) - .map((ref) => find.track(ref, trackData, {mode: 'quiet'})) - .filter(Boolean) - : [], - }, - }, + commentatorArtists: commentatorArtists(), - groups: Thing.common.dynamicThingsFromReferenceList( - 'groupsByRef', - 'groupData', - find.group - ), - - artTags: Thing.common.dynamicThingsFromReferenceList( - 'artTagsByRef', - 'artTagData', - find.artTag - ), + hasCoverArt: contribsPresent({contribs: 'coverArtistContribs'}), + hasWallpaperArt: contribsPresent({contribs: 'wallpaperArtistContribs'}), + hasBannerArt: contribsPresent({contribs: 'bannerArtistContribs'}), + + tracks: [ + withTracks(), + exposeDependency({dependency: '#tracks'}), + ], }); static [Thing.getSerializeDescriptors] = ({ @@ -159,10 +202,10 @@ export class Album extends Thing { bannerDimensions: S.id, hasTrackArt: S.id, - isMajorRelease: S.id, isListedOnHomepage: S.id, - commentary: S.id, + commentary: S.toCommentaryRefs, + additionalFiles: S.id, tracks: S.toRefs, @@ -170,74 +213,222 @@ export class Album extends Thing { artTags: S.toRefs, commentatorArtists: S.toRefs, }); -} - -export class TrackGroup extends Thing { - static [Thing.getPropertyDescriptors] = ({ - isColor, - Track, - validators: { - validateInstanceOf, + static [Thing.findSpecs] = { + album: { + referenceTypes: ['album', 'album-commentary', 'album-gallery'], + bindTo: 'albumData', }, - }) => ({ - // Update & expose + }; + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Album': {property: 'name'}, + 'Directory': {property: 'directory'}, - name: Thing.common.name('Unnamed Track Group'), + 'Bandcamp Album ID': { + property: 'bandcampAlbumIdentifier', + transform: String, + }, + + 'Bandcamp Artwork ID': { + property: 'bandcampArtworkIdentifier', + transform: String, + }, - color: { - flags: {update: true, expose: true}, + 'Date': { + property: 'date', + transform: parseDate, + }, - update: {validate: isColor}, + 'Color': {property: 'color'}, + 'URLs': {property: 'urls'}, - expose: { - dependencies: ['album'], + 'Has Track Numbers': {property: 'hasTrackNumbers'}, + 'Listed on Homepage': {property: 'isListedOnHomepage'}, + 'Listed in Galleries': {property: 'isListedInGalleries'}, - transform(color, {album}) { - return color ?? album?.color ?? null; - }, + 'Cover Art Date': { + property: 'coverArtDate', + transform: parseDate, }, - }, - dateOriginallyReleased: Thing.common.simpleDate(), + 'Default Track Cover Art Date': { + property: 'trackArtDate', + transform: parseDate, + }, - tracksByRef: Thing.common.referenceList(Track), + 'Date Added': { + property: 'dateAddedToWiki', + transform: parseDate, + }, - isDefaultTrackGroup: Thing.common.flag(false), + 'Cover Art File Extension': {property: 'coverArtFileExtension'}, + 'Track Art File Extension': {property: 'trackCoverArtFileExtension'}, - // Update only + 'Cover Art Dimensions': { + property: 'coverArtDimensions', + transform: parseDimensions, + }, - album: { - flags: {update: true}, - update: {validate: validateInstanceOf(Album)}, - }, + 'Wallpaper Artists': { + property: 'wallpaperArtistContribs', + transform: parseContributors, + }, - trackData: Thing.common.wikiData(Track), + 'Wallpaper Style': {property: 'wallpaperStyle'}, + 'Wallpaper File Extension': {property: 'wallpaperFileExtension'}, - // Expose only + 'Banner Artists': { + property: 'bannerArtistContribs', + transform: parseContributors, + }, - tracks: { - flags: {expose: true}, + 'Banner Style': {property: 'bannerStyle'}, + 'Banner File Extension': {property: 'bannerFileExtension'}, - expose: { - dependencies: ['tracksByRef', 'trackData'], - compute: ({tracksByRef, trackData}) => - tracksByRef && trackData - ? tracksByRef.map((ref) => find.track(ref, trackData)).filter(Boolean) - : [], + 'Banner Dimensions': { + property: 'bannerDimensions', + transform: parseDimensions, }, - }, - startIndex: { - flags: {expose: true}, + 'Commentary': {property: 'commentary'}, + + 'Additional Files': { + property: 'additionalFiles', + transform: parseAdditionalFiles, + }, + + 'Franchises': {ignore: true}, + + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, - expose: { - dependencies: ['album'], - compute: ({album, [TrackGroup.instance]: trackGroup}) => - album.trackGroups - .slice(0, album.trackGroups.indexOf(trackGroup)) - .reduce((acc, tg) => acc + tg.tracks.length, 0), + 'Cover Artists': { + property: 'coverArtistContribs', + transform: parseContributors, }, + + 'Default Track Cover Artists': { + property: 'trackCoverArtistContribs', + transform: parseContributors, + }, + + 'Groups': {property: 'groups'}, + 'Art Tags': {property: 'artTags'}, + + 'Review Points': {ignore: true}, + }, + }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {headerAndEntries}, + thingConstructors: {Album, Track, TrackSectionHelper}, + }) => ({ + title: `Process album files`, + + files: dataPath => + traverse(path.join(dataPath, DATA_ALBUM_DIRECTORY), { + filterFile: name => path.extname(name) === '.yaml', + prefixPath: DATA_ALBUM_DIRECTORY, + }), + + documentMode: headerAndEntries, + headerDocumentThing: Album, + entryDocumentThing: document => + ('Section' in document + ? TrackSectionHelper + : Track), + + save(results) { + const albumData = []; + const trackData = []; + + for (const {header: album, entries} of results) { + // We can't mutate an array once it's set as a property value, + // so prepare the track sections that will show up in a track list + // all the way before actually applying them. (It's okay to mutate + // an individual section before applying it, since those are just + // generic objects; they aren't Things in and of themselves.) + const trackSections = []; + const ownTrackData = []; + + let currentTrackSection = { + name: `Default Track Section`, + isDefaultTrackSection: true, + tracks: [], + }; + + const albumRef = Thing.getReference(album); + + const closeCurrentTrackSection = () => { + if (!empty(currentTrackSection.tracks)) { + trackSections.push(currentTrackSection); + } + }; + + for (const entry of entries) { + if (entry instanceof TrackSectionHelper) { + closeCurrentTrackSection(); + + currentTrackSection = { + name: entry.name, + color: entry.color, + dateOriginallyReleased: entry.dateOriginallyReleased, + isDefaultTrackSection: false, + tracks: [], + }; + + continue; + } + + trackData.push(entry); + + entry.dataSourceAlbum = albumRef; + + ownTrackData.push(entry); + currentTrackSection.tracks.push(Thing.getReference(entry)); + } + + closeCurrentTrackSection(); + + albumData.push(album); + + album.trackSections = trackSections; + album.ownTrackData = ownTrackData; + } + + return {albumData, trackData}; + }, + + sort({albumData, trackData}) { + sortChronologically(albumData); + sortAlbumsTracksChronologically(trackData); }, + }); +} + +export class TrackSectionHelper extends Thing { + static [Thing.friendlyName] = `Track Section`; + + static [Thing.getPropertyDescriptors] = () => ({ + name: name('Unnamed Track Section'), + color: color(), + dateOriginallyReleased: simpleDate(), + isDefaultTrackGroup: flag(false), }) + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Section': {property: 'name'}, + 'Color': {property: 'color'}, + + 'Date Originally Released': { + property: 'dateOriginallyReleased', + transform: parseDate, + }, + }, + }; } |