diff options
Diffstat (limited to 'src/content')
9 files changed, 431 insertions, 128 deletions
diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index cf8ce994..29bc34e6 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -14,6 +14,11 @@ export default { ...artist.trackCoverArtistContributions, ], + musicVideoContributions: [ + ...artist.musicVideoArtistContributions, + ...artist.musicVideoContributorContributions, + ], + // Banners and wallpapers don't show up in the artist gallery page, only // cover art. hasGallery: @@ -79,6 +84,12 @@ export default { ? relation('linkArtistGallery', artist) : null), + musicVideosChunkedList: + relation('generateArtistInfoPageMusicVideosChunkedList', artist), + + musicVideosGroupInfo: + relation('generateArtistGroupContributionsInfo', query.musicVideoContributions), + flashesChunkedList: relation('generateArtistInfoPageFlashesChunkedList', artist), @@ -216,6 +227,11 @@ export default { {href: '#art'}, language.$(pageCapsule, 'artList.title')), + !html.isBlank(relations.musicVideosChunkedList) && + html.tag('a', + {href: '#music-videos'}, + language.$(pageCapsule, 'musicVideoList.title')), + !html.isBlank(relations.flashesChunkedList) && html.tag('a', {href: '#flashes'}, @@ -329,6 +345,26 @@ export default { relations.contentHeading.clone() .slots({ tag: 'h2', + attributes: {id: 'music-videos'}, + title: language.$(pageCapsule, 'musicVideoList.title'), + }), + + relations.musicVideosChunkedList.slots({ + groupInfo: + language.encapsulate(pageCapsule, 'groupContributions', capsule => + relations.musicVideosGroupInfo.slots({ + title: language.$(capsule, 'title.artworks'), + showBothColumns: false, + sort: 'count', + countUnit: 'artworks', + })), + }), + ]), + + html.tags([ + relations.contentHeading.clone() + .slots({ + tag: 'h2', attributes: {id: 'flashes'}, title: language.$(pageCapsule, 'flashList.title'), }), diff --git a/src/content/dependencies/generateArtistInfoPageChunk.js b/src/content/dependencies/generateArtistInfoPageChunk.js index 3fa46c61..e19030c9 100644 --- a/src/content/dependencies/generateArtistInfoPageChunk.js +++ b/src/content/dependencies/generateArtistInfoPageChunk.js @@ -18,30 +18,30 @@ export default { mutable: false, }, - dates: { - validate: v => v.sparseArrayOf(v.isDate), - }, + // Container and items, respectively. + date: {validate: v => v.isDate}, + dates: {validate: v => v.sparseArrayOf(v.isDate)}, duration: {validate: v => v.isDuration}, durationApproximate: {type: 'boolean'}, }, generate(slots, {html, language}) { - let earliestDate = null; - let latestDate = null; - let onlyDate = null; + let earliestItemDate = null; + let latestItemDate = null; + let onlyItemDate = null; if (!empty(slots.dates)) { - earliestDate = - slots.dates - .reduce((a, b) => a <= b ? a : b); + earliestItemDate = slots.dates[0]; + latestItemDate = slots.dates[1]; - latestDate = - slots.dates - .reduce((a, b) => a <= b ? b : a); + for (const date of slots.dates.slice(1)) { + if (date < earliestItemDate) earliestItemDate = date; + if (date > latestItemDate) latestItemDate = date; + } - if (+earliestDate === +latestDate) { - onlyDate = earliestDate; + if (+earliestItemDate === +latestItemDate) { + onlyItemDate = earliestItemDate; } } @@ -51,9 +51,16 @@ export default { const options = {album: slots.link}; const parts = ['artistPage.creditList.album']; - if (onlyDate) { + if (slots.date) { + parts.push('withDate'); + options.date = language.formatDate(slots.date); + } else if (onlyItemDate) { parts.push('withDate'); - options.date = language.formatDate(onlyDate); + options.date = language.formatDate(onlyItemDate); + } else if (earliestItemDate && latestItemDate) { + parts.push('withDateRange'); + options.dateRange = + language.formatDateRange(earliestItemDate, latestItemDate); } if (slots.duration) { @@ -72,13 +79,13 @@ export default { const options = {act: slots.link}; const parts = ['artistPage.creditList.flashAct']; - if (onlyDate) { + if (onlyItemDate) { parts.push('withDate'); - options.date = language.formatDate(onlyDate); - } else if (earliestDate && latestDate) { + options.date = language.formatDate(onlyItemDate); + } else if (earliestItemDate && latestItemDate) { parts.push('withDateRange'); options.dateRange = - language.formatDateRange(earliestDate, latestDate); + language.formatDateRange(earliestItemDate, latestItemDate); } accentedLink = language.formatString(...parts, options); diff --git a/src/content/dependencies/generateArtistInfoPageMusicVideosChunk.js b/src/content/dependencies/generateArtistInfoPageMusicVideosChunk.js new file mode 100644 index 00000000..6912d4d6 --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageMusicVideosChunk.js @@ -0,0 +1,58 @@ +export default { + relations: (relation, artist, album, contribs) => ({ + template: + relation('generateArtistInfoPageChunk'), + + albumLink: + relation('linkAlbum', album), + + albumArtistCredit: + relation('generateArtistCredit', album.artistContribs, []), + + items: + contribs.map(contribs => + relation('generateArtistInfoPageMusicVideosChunkItem', + artist, + contribs)), + }), + + data: (_artist, album, contribs) => ({ + albumDate: + album.date, + + contribDates: + contribs + .flat() + .map(contrib => contrib.date), + }), + + generate: (data, relations, {html, language}) => + relations.template.slots({ + mode: 'album', + + link: + language.encapsulate('artistPage.creditList.album', workingCapsule => { + const creditCapsule = workingCapsule + '.credit'; + const workingOptions = {album: relations.albumLink}; + + relations.albumArtistCredit.setSlots({ + normalStringKey: creditCapsule + '.by', + }); + + if (!html.isBlank(relations.albumArtistCredit)) { + workingCapsule += '.withCredit'; + workingOptions.credit = + html.tag('span', {class: 'by'}, + relations.albumArtistCredit); + } + + return language.$(workingCapsule, workingOptions); + }), + + date: data.albumDate, + dates: data.contribDates, + + list: + html.tag('ul', relations.items), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js new file mode 100644 index 00000000..8bae860d --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js @@ -0,0 +1,118 @@ +import {empty} from '#sugar'; +import {selectRepresentativeArtistContributorContribs} from '#wiki-data'; + +export default { + query(_artist, contribs) { + const query = {}; + + query.musicVideo = contribs[0].thing; + + query.albumOrTrack = query.musicVideo.thing; + + query.album = + (query.albumOrTrack.isAlbum + ? query.albumOrTrack + : query.albumOrTrack.album); + + query.displayedContributions = + selectRepresentativeArtistContributorContribs(contribs); + + return query; + }, + + relations: (relation, query, artist, _contribs) => ({ + template: + relation('generateArtistInfoPageChunkItem'), + + trackLink: + (query.albumOrTrack.isTrack + ? relation('linkTrack', query.albumOrTrack) + : null), + + artistCredit: + relation('generateArtistCredit', + query.musicVideo.artistContribs, + (empty(query.album.artistContribs) + ? [artist.mockSimpleContribution] + : query.album.artistContribs)), + + externalLinks: + query.musicVideo.urls + .map(url => relation('linkExternal', url)), + }), + + data: (query, _artist, contribs) => ({ + date: contribs[0].date, + + for: + (query.albumOrTrack.isAlbum + ? 'album' + : 'track'), + + title: query.musicVideo.title, + label: query.musicVideo.label, + + contribAnnotationParts: + (query.displayedContributions + ? query.displayedContributions + .flatMap(contrib => contrib.annotationParts) + : null), + }), + + generate: (data, relations, {html, language}) => + relations.template.slots({ + annotation: + (data.contribAnnotationParts + ? language.formatUnitList(data.contribAnnotationParts) + : html.blank()), + + content: + language.encapsulate('artistPage.creditList.entry', entryCapsule => { + let workingCapsule = entryCapsule; + let workingOptions = {}; + + workingCapsule += '.' + data.for + '.musicVideo'; + + const musicVideoCapsule = workingCapsule; + + if (data.for === 'track') { + workingOptions.track = + relations.trackLink; + } + + if (data.date) { + workingCapsule += '.withDate'; + workingOptions.date = language.formatDate(data.date); + } + + relations.artistCredit.setSlots({ + normalStringKey: + musicVideoCapsule + '.credit' + + (data.title ? '.alongsideTitle' + : data.label ? '.alongsideLabel' + : ''), + }); + + if (!html.isBlank(relations.artistCredit)) { + workingCapsule += '.withCredit'; + workingOptions.credit = relations.artistCredit; + } + + if (data.title) { + workingCapsule += '.withTitle'; + workingOptions.title = language.sanitize(data.title); + } else if (data.label) { + workingCapsule += '.withLabel'; + workingOptions.label = language.sanitize(data.label); + } + + if (!empty(relations.externalLinks)) { + workingCapsule += '.withLinks'; + workingOptions.links = + language.formatUnitList(relations.externalLinks); + } + + return language.$(workingCapsule, workingOptions); + }), + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkedList.js b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkedList.js new file mode 100644 index 00000000..588fbbeb --- /dev/null +++ b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkedList.js @@ -0,0 +1,66 @@ +import {chunkByConditions, stitchArrays} from '#sugar'; +import {sortAlbumsTracksChronologically, sortContributionsChronologically} + from '#sort'; + +export default { + query(artist) { + const query = {}; + + const allContributions = [ + ...artist.musicVideoArtistContributions, + ...artist.musicVideoContributorContributions, + ...artist.otherMusicVideoArtistContributionsToOwnAlbums, + ]; + + const getMusicVideo = contrib => + contrib.thing; + + const getAlbumOrTrack = contrib => + getMusicVideo(contrib).thing; + + sortContributionsChronologically( + allContributions, + sortAlbumsTracksChronologically, + {getThing: getAlbumOrTrack}); + + const getAlbum = contrib => + (getAlbumOrTrack(contrib).isTrack + ? getAlbumOrTrack(contrib).album + : getAlbumOrTrack(contrib)); + + query.contribs = + chunkByConditions(allContributions, [ + (a, b) => getAlbum(a) !== getAlbum(b), + ]).map(contribs => + chunkByConditions(contribs, [ + (a, b) => getMusicVideo(a) !== getMusicVideo(b), + ])); + + query.albums = + query.contribs + .map(contribs => contribs[0][0]) + .map(contrib => getAlbum(contrib)); + + return query; + }, + + relations: (relation, query, artist) => ({ + template: + relation('generateArtistInfoPageChunkedList'), + + chunks: + stitchArrays({ + album: query.albums, + contribs: query.contribs, + }).map(({album, contribs}) => + relation('generateArtistInfoPageMusicVideosChunk', + artist, + album, + contribs)), + }), + + generate: (relations) => + relations.template.slots({ + chunks: relations.chunks, + }), +}; diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunk.js b/src/content/dependencies/generateArtistInfoPageTracksChunk.js index b3727756..7a7fc6a9 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunk.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunk.js @@ -61,7 +61,7 @@ export default { .filter(contribs => countTowardTrackTotals(contribs) === false), }), - relations: (relation, query, artist, album, _trackContribLists) => ({ + relations: (relation, query, artist, album, trackContribLists) => ({ template: relation('generateArtistInfoPageChunk'), @@ -82,13 +82,15 @@ export default { query.contribListsCountingTowardTotals.map(trackContribs => relation('generateArtistInfoPageTracksChunkItem', artist, - trackContribs)), + trackContribs, + trackContribLists)), itemsNotCountingTowardTotals: query.contribListsNotCountingTowardTotals.map(trackContribs => relation('generateArtistInfoPageTracksChunkItem', artist, - trackContribs)), + trackContribs, + trackContribLists)), }), data(artist, _query, album, trackContribLists) { @@ -97,7 +99,10 @@ export default { const contribs = trackContribLists.flat(); - data.dates = + data.albumDate = + album.date; + + data.contribDates = contribs .map(contrib => contrib.date); @@ -168,7 +173,9 @@ export default { return language.$(workingCapsule, workingOptions); }), - dates: data.dates, + date: data.albumDate, + dates: data.contribDates, + duration: data.duration, durationApproximate: data.durationApproximate, diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js b/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js index 3d6e274b..22a4a228 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunkItem.js @@ -1,60 +1,23 @@ import {sortAlbumsTracksChronologically} from '#sort'; import {empty} from '#sugar'; +import {selectRepresentativeArtistContributorContribs} from '#wiki-data'; export default { - query(artist, contribs) { + query(artist, contribs, chunkContribs) { const query = {}; - // TODO: Very mysterious what to do if the set of contributions is, - // in total, associated with more than one thing. No design yet. query.track = contribs[0].thing; - const creditedAsNormalArtist = - contribs - .some(contrib => - contrib.thingProperty === 'artistContribs' && - !contrib.isFeaturingCredit); - - const creditedAsContributor = - contribs - .some(contrib => contrib.thingProperty === 'contributorContribs'); - - const annotatedContribs = - contribs - .filter(contrib => !empty(contrib.annotationParts)); - - const annotatedArtistContribs = - annotatedContribs - .filter(contrib => contrib.thingProperty === 'artistContribs'); - - const annotatedContributorContribs = - annotatedContribs - .filter(contrib => contrib.thingProperty === 'contributorContribs'); - - // Don't display annotations associated with crediting in the - // Contributors field if the artist is also credited as an Artist - // *and* the Artist-field contribution is non-annotated. This is - // so that we don't misrepresent the artist - the contributor - // annotation tends to be for "secondary" and performance roles. - // For example, this avoids crediting Marcy Nabors on Renewed - // Return seemingly only for "bass clarinet" when they're also - // the one who composed and arranged Renewed Return! - if ( - creditedAsNormalArtist && - creditedAsContributor && - empty(annotatedArtistContribs) - ) { - query.displayedContributions = null; - } else if ( - !empty(annotatedArtistContribs) || - !empty(annotatedContributorContribs) - ) { - query.displayedContributions = [ - ...annotatedArtistContribs, - ...annotatedContributorContribs, - ]; - } + query.date = + contribs[0].date; + + query.anyItemsExpresslyDated = + chunkContribs.flat() + .some(contrib => +contrib.date !== +query.track.album.date); + + query.displayedContributions = + selectRepresentativeArtistContributorContribs(contribs); // It's kinda awkward to perform this chronological sort here, // per track, rather than just reusing the one that's done to @@ -112,6 +75,11 @@ export default { }), data: (query) => ({ + date: + (query.anyItemsExpresslyDated + ? query.date + : null), + duration: query.track.duration, @@ -146,6 +114,7 @@ export default { relations.trackListItem.slots({ showArtists: 'auto', showDuration: slots.showDuration, + showDate: data.date, })), }), }), diff --git a/src/content/dependencies/generateTrackListItem.js b/src/content/dependencies/generateTrackListItem.js index c8c57534..383f0025 100644 --- a/src/content/dependencies/generateTrackListItem.js +++ b/src/content/dependencies/generateTrackListItem.js @@ -25,6 +25,9 @@ export default { }), data: (track, _contextContributions) => ({ + date: + track.date, + duration: track.duration ?? 0, @@ -47,6 +50,11 @@ export default { default: false, }, + showDate: { + validate: v => v.anyOf(v.isBoolean, v.isDate), + default: false, + }, + colorMode: { validate: v => v.is('none', 'track', 'line'), default: 'track', @@ -62,48 +70,83 @@ export default { language.encapsulate(itemCapsule, workingCapsule => { const workingOptions = {}; - workingOptions.track = - relations.trackLink - .slot('color', slots.colorMode === 'track'); + const accent = + language.encapsulate(itemCapsule, 'accent', accentCapsule => { + let workingCapsule = accentCapsule; + let workingOptions = {}; + let any = false; + + if (slots.showDate) { + any = true; + workingCapsule += '.withDate'; + workingOptions.date = + language.$(accentCapsule, 'date', { + date: + (slots.showDate === true + ? language.formatDate(data.date) + : language.formatDate(slots.showDate)), + }); + } + + if (slots.showDuration) { + any = true; + workingCapsule += '.withDuration'; + workingOptions.duration = + (data.trackHasDuration + ? language.$(accentCapsule, 'duration', { + duration: + language.formatDuration(data.duration), + }) + : relations.missingDuration); + } + + if (any) { + return language.$(workingCapsule, workingOptions); + } else { + return html.blank(); + } + }); - if (slots.showDuration) { - workingCapsule += '.withDuration'; - workingOptions.duration = - (data.trackHasDuration - ? language.$(itemCapsule, 'withDuration.duration', { - duration: - language.formatDuration(data.duration), - }) - : relations.missingDuration); + if (!html.isBlank(accent)) { + workingCapsule += '.withAccent'; + workingOptions.accent = accent; } - const chosenCredit = - (slots.showArtists === true - ? relations.acontextualCredit - : slots.showArtists === 'auto' - ? relations.contextualCredit - : null); - - if (chosenCredit) { - const artistCapsule = language.encapsulate(itemCapsule, 'withArtists'); - - chosenCredit.setSlots({ - normalStringKey: - artistCapsule + '.by', - - featuringStringKey: - artistCapsule + '.featuring', + workingOptions.track = + relations.trackLink + .slot('color', slots.colorMode === 'track'); - normalFeaturingStringKey: - artistCapsule + '.by.featuring', + const artists = + language.encapsulate(itemCapsule, 'artists', artistsCapsule => { + const chosenCredit = + (slots.showArtists === true + ? relations.acontextualCredit + : slots.showArtists === 'auto' + ? relations.contextualCredit + : null); + + if (!chosenCredit) { + return html.blank(); + } + + // This might still be blank, if the contextual credit is chosen + // and it matches its context credit. + return chosenCredit.slots({ + normalStringKey: + artistsCapsule + '.by', + + featuringStringKey: + artistsCapsule + '.featuring', + + normalFeaturingStringKey: + artistsCapsule + '.by.featuring', + }); }); - if (!html.isBlank(chosenCredit)) { - workingCapsule += '.withArtists'; - workingOptions.by = - html.tag('span', {class: 'by'}, - chosenCredit); - } + if (!html.isBlank(artists)) { + workingCapsule += '.withArtists'; + workingOptions.artists = + html.tag('span', {class: 'by'}, artists); } return language.$(workingCapsule, workingOptions); diff --git a/src/content/dependencies/generateTrackListMissingDuration.js b/src/content/dependencies/generateTrackListMissingDuration.js index 70db23c2..f3c5c6ce 100644 --- a/src/content/dependencies/generateTrackListMissingDuration.js +++ b/src/content/dependencies/generateTrackListMissingDuration.js @@ -8,27 +8,26 @@ export default { }), generate: (relations, {html, language}) => - language.encapsulate('trackList.item.withDuration', itemCapsule => - language.encapsulate(itemCapsule, 'duration', durationCapsule => - relations.textWithTooltip.slots({ - attributes: {class: 'missing-duration'}, - customInteractionCue: true, + language.encapsulate('trackList.item.accent.duration', capsule => + relations.textWithTooltip.slots({ + attributes: {class: 'missing-duration'}, + customInteractionCue: true, - text: - language.$(durationCapsule, { - duration: - html.tag('span', {class: 'text-with-tooltip-interaction-cue'}, - {tabindex: '0'}, + text: + language.$(capsule, { + duration: + html.tag('span', {class: 'text-with-tooltip-interaction-cue'}, + {tabindex: '0'}, - language.$(durationCapsule, 'missing')), - }), + language.$(capsule, 'missing')), + }), - tooltip: - relations.tooltip.slots({ - attributes: {class: 'missing-duration-tooltip'}, + tooltip: + relations.tooltip.slots({ + attributes: {class: 'missing-duration-tooltip'}, - content: - language.$(durationCapsule, 'missing.info'), - }), - }))), + content: + language.$(capsule, 'missing.info'), + }), + })), }; |