diff options
Diffstat (limited to 'src/content/dependencies/generateArtistGroupContributionsInfo.js')
-rw-r--r-- | src/content/dependencies/generateArtistGroupContributionsInfo.js | 325 |
1 files changed, 174 insertions, 151 deletions
diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js index 1725d4b9..e1fa7a0b 100644 --- a/src/content/dependencies/generateArtistGroupContributionsInfo.js +++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js @@ -1,83 +1,90 @@ -import {empty, filterProperties, stitchArrays, unique} from '#sugar'; +import {accumulateSum, empty, stitchArrays, withEntries} from '#sugar'; export default { contentDependencies: ['linkGroup'], extraDependencies: ['html', 'language', 'wikiData'], - sprawl({groupCategoryData}) { - return { - groupOrder: groupCategoryData.flatMap(category => category.groups), - } - }, + sprawl: ({groupCategoryData}) => ({ + groupOrder: + groupCategoryData.flatMap(category => category.groups), + }), - query(sprawl, tracksAndAlbums) { - const filteredAlbums = tracksAndAlbums.filter(thing => !thing.album); - const filteredTracks = tracksAndAlbums.filter(thing => thing.album); + query(sprawl, contributions) { + const allGroupsUnordered = + new Set(contributions.flatMap(contrib => contrib.groups)); - const allAlbums = unique([ - ...filteredAlbums, - ...filteredTracks.map(track => track.album), - ]); + const allGroupsOrdered = + sprawl.groupOrder.filter(group => allGroupsUnordered.has(group)); - const allGroupsUnordered = new Set(Array.from(allAlbums).flatMap(album => album.groups)); - const allGroupsOrdered = sprawl.groupOrder.filter(group => allGroupsUnordered.has(group)); + const groupToThingsCountedForContributions = + new Map(allGroupsOrdered.map(group => [group, new Set])); - const mapTemplate = allGroupsOrdered.map(group => [group, 0]); - const groupToCountMap = new Map(mapTemplate); - const groupToDurationMap = new Map(mapTemplate); - const groupToDurationCountMap = new Map(mapTemplate); + const groupToThingsCountedForDuration = + new Map(allGroupsOrdered.map(group => [group, new Set])); - for (const album of filteredAlbums) { - for (const group of album.groups) { - groupToCountMap.set(group, groupToCountMap.get(group) + 1); - } - } + for (const contrib of contributions) { + for (const group of contrib.groups) { + if (contrib.countInContributionTotals) { + groupToThingsCountedForContributions.get(group).add(contrib.thing); + } - for (const track of filteredTracks) { - for (const group of track.album.groups) { - groupToCountMap.set(group, groupToCountMap.get(group) + 1); - if (track.duration && track.originalReleaseTrack === null) { - groupToDurationMap.set(group, groupToDurationMap.get(group) + track.duration); - groupToDurationCountMap.set(group, groupToDurationCountMap.get(group) + 1); + if (contrib.countInDurationTotals) { + groupToThingsCountedForDuration.get(group).add(contrib.thing); } } } + const groupToTotalContributions = + withEntries( + groupToThingsCountedForContributions, + entries => entries.map( + ([group, things]) => + ([group, things.size]))); + + const groupToTotalDuration = + withEntries( + groupToThingsCountedForDuration, + entries => entries.map( + ([group, things]) => + ([group, accumulateSum(things, thing => thing.duration)]))) + const groupsSortedByCount = allGroupsOrdered - .slice() - .sort((a, b) => groupToCountMap.get(b) - groupToCountMap.get(a)); + .filter(group => groupToTotalContributions.get(group) > 0) + .sort((a, b) => + (groupToTotalContributions.get(b) + - groupToTotalContributions.get(a))); - // The filter here ensures all displayed groups have at least some duration - // when sorting by duration. const groupsSortedByDuration = allGroupsOrdered - .filter(group => groupToDurationMap.get(group) > 0) - .sort((a, b) => groupToDurationMap.get(b) - groupToDurationMap.get(a)); + .filter(group => groupToTotalDuration.get(group) > 0) + .sort((a, b) => + (groupToTotalDuration.get(b) + - groupToTotalDuration.get(a))); const groupCountsSortedByCount = groupsSortedByCount - .map(group => groupToCountMap.get(group)); + .map(group => groupToTotalContributions.get(group)); const groupDurationsSortedByCount = groupsSortedByCount - .map(group => groupToDurationMap.get(group)); + .map(group => groupToTotalDuration.get(group)); const groupDurationsApproximateSortedByCount = groupsSortedByCount - .map(group => groupToDurationCountMap.get(group) > 1); + .map(group => groupToThingsCountedForDuration.get(group).size > 1); const groupCountsSortedByDuration = groupsSortedByDuration - .map(group => groupToCountMap.get(group)); + .map(group => groupToTotalContributions.get(group)); const groupDurationsSortedByDuration = groupsSortedByDuration - .map(group => groupToDurationMap.get(group)); + .map(group => groupToTotalDuration.get(group)); const groupDurationsApproximateSortedByDuration = groupsSortedByDuration - .map(group => groupToDurationCountMap.get(group) > 1); + .map(group => groupToThingsCountedForDuration.get(group).size > 1); return { groupsSortedByCount, @@ -93,29 +100,35 @@ export default { }; }, - relations(relation, query) { - return { - groupLinksSortedByCount: - query.groupsSortedByCount - .map(group => relation('linkGroup', group)), + relations: (relation, query) => ({ + groupLinksSortedByCount: + query.groupsSortedByCount + .map(group => relation('linkGroup', group)), - groupLinksSortedByDuration: - query.groupsSortedByDuration - .map(group => relation('linkGroup', group)), - }; - }, + groupLinksSortedByDuration: + query.groupsSortedByDuration + .map(group => relation('linkGroup', group)), + }), - data(query) { - return filterProperties(query, [ - 'groupCountsSortedByCount', - 'groupDurationsSortedByCount', - 'groupDurationsApproximateSortedByCount', + data: (query) => ({ + groupCountsSortedByCount: + query.groupCountsSortedByCount, - 'groupCountsSortedByDuration', - 'groupDurationsSortedByDuration', - 'groupDurationsApproximateSortedByDuration', - ]); - }, + groupDurationsSortedByCount: + query.groupDurationsSortedByCount, + + groupDurationsApproximateSortedByCount: + query.groupDurationsApproximateSortedByCount, + + groupCountsSortedByDuration: + query.groupCountsSortedByDuration, + + groupDurationsSortedByDuration: + query.groupDurationsSortedByDuration, + + groupDurationsApproximateSortedByDuration: + query.groupDurationsApproximateSortedByDuration, + }), slots: { title: { @@ -131,94 +144,104 @@ export default { countUnit: {validate: v => v.is('tracks', 'artworks')}, }, - generate(data, relations, slots, {html, language}) { - if (slots.sort === 'count' && empty(relations.groupLinksSortedByCount)) { - return html.blank(); - } else if (slots.sort === 'duration' && empty(relations.groupLinksSortedByDuration)) { - return html.blank(); - } + generate: (data, relations, slots, {html, language}) => + language.encapsulate('artistPage.groupContributions', capsule => { + if (slots.sort === 'count' && empty(relations.groupLinksSortedByCount)) { + return html.blank(); + } else if (slots.sort === 'duration' && empty(relations.groupLinksSortedByDuration)) { + return html.blank(); + } - const getCounts = counts => - counts.map(count => { - switch (slots.countUnit) { - case 'tracks': return language.countTracks(count, {unit: true}); - case 'artworks': return language.countArtworks(count, {unit: true}); - } - }); - - // We aren't displaying the "~" approximate symbol here for now. - // The general notion that these sums aren't going to be 100% accurate - // is made clear by the "XYZ has contributed ~1:23:45 hours of music..." - // line that's always displayed above this table. - const getDurations = (durations, approximate) => - stitchArrays({ - duration: durations, - approximate: approximate, - }).map(({duration}) => language.formatDuration(duration)); - - const topLevelClasses = [ - 'group-contributions-sorted-by-' + slots.sort, - slots.visible && 'visible', - ]; - - return html.tags([ - html.tag('dt', {class: topLevelClasses}, - (slots.showSortButton - ? language.$('artistPage.groupContributions.title.withSortButton', { - title: slots.title, - sort: - html.tag('a', {class: 'group-contributions-sort-button'}, - {href: '#'}, - - (slots.sort === 'count' - ? language.$('artistPage.groupContributions.title.sorting.count') - : language.$('artistPage.groupContributions.title.sorting.duration'))), - }) - : slots.title)), - - html.tag('dd', {class: topLevelClasses}, - html.tag('ul', {class: 'group-contributions-table'}, - {role: 'list'}, - - (slots.sort === 'count' - ? stitchArrays({ - group: relations.groupLinksSortedByCount, - count: getCounts(data.groupCountsSortedByCount), - duration: - getDurations( - data.groupDurationsSortedByCount, - data.groupDurationsApproximateSortedByCount), - }).map(({group, count, duration}) => - html.tag('li', - html.tag('div', {class: 'group-contributions-row'}, [ - group, - html.tag('span', {class: 'group-contributions-metrics'}, - // When sorting by count, duration details aren't necessarily - // available for all items. - (slots.showBothColumns && duration - ? language.$('artistPage.groupContributions.item.countDurationAccent', {count, duration}) - : language.$('artistPage.groupContributions.item.countAccent', {count}))), - ]))) - - : stitchArrays({ - group: relations.groupLinksSortedByDuration, - count: getCounts(data.groupCountsSortedByDuration), - duration: - getDurations( - data.groupDurationsSortedByDuration, - data.groupDurationsApproximateSortedByDuration), - }).map(({group, count, duration}) => - html.tag('li', - html.tag('div', {class: 'group-contributions-row'}, [ - group, - html.tag('span', {class: 'group-contributions-metrics'}, - // Count details are always available, since they're just the - // number of contributions directly. And duration details are - // guaranteed for every item when sorting by duration. - (slots.showBothColumns - ? language.$('artistPage.groupContributions.item.durationCountAccent', {duration, count}) - : language.$('artistPage.groupContributions.item.durationAccent', {duration}))), - ])))))), - ]); - }, + const getCounts = counts => + counts.map(count => { + switch (slots.countUnit) { + case 'tracks': return language.countTracks(count, {unit: true}); + case 'artworks': return language.countArtworks(count, {unit: true}); + } + }); + + // We aren't displaying the "~" approximate symbol here for now. + // The general notion that these sums aren't going to be 100% accurate + // is made clear by the "XYZ has contributed ~1:23:45 hours of music..." + // line that's always displayed above this table. + const getDurations = (durations, approximate) => + stitchArrays({ + duration: durations, + approximate: approximate, + }).map(({duration}) => language.formatDuration(duration)); + + const topLevelClasses = [ + 'group-contributions-sorted-by-' + slots.sort, + slots.visible && 'visible', + ]; + + // TODO: It feels pretty awkward that this component is the only one that + // has enough knowledge to decide if the sort button is even applicable... + const switchingSortPossible = + !empty(relations.groupLinksSortedByCount) && + !empty(relations.groupLinksSortedByDuration); + + return html.tags([ + html.tag('dt', {class: topLevelClasses}, + language.encapsulate(capsule, 'title', capsule => + (switchingSortPossible && slots.showSortButton + ? language.$(capsule, 'withSortButton', { + title: slots.title, + sort: + html.tag('a', {class: 'group-contributions-sort-button'}, + {href: '#'}, + + (slots.sort === 'count' + ? language.$(capsule, 'sorting.count') + : language.$(capsule, 'sorting.duration'))), + }) + : slots.title))), + + html.tag('dd', {class: topLevelClasses}, + html.tag('ul', {class: 'group-contributions-table'}, + {role: 'list'}, + + (slots.sort === 'count' + ? stitchArrays({ + group: relations.groupLinksSortedByCount, + count: getCounts(data.groupCountsSortedByCount), + duration: + getDurations( + data.groupDurationsSortedByCount, + data.groupDurationsApproximateSortedByCount), + }).map(({group, count, duration}) => + language.encapsulate(capsule, 'item', capsule => + html.tag('li', + html.tag('div', {class: 'group-contributions-row'}, [ + group, + html.tag('span', {class: 'group-contributions-metrics'}, + // When sorting by count, duration details aren't necessarily + // available for all items. + (slots.showBothColumns && duration + ? language.$(capsule, 'countDurationAccent', {count, duration}) + : language.$(capsule, 'countAccent', {count}))), + ])))) + + : stitchArrays({ + group: relations.groupLinksSortedByDuration, + count: getCounts(data.groupCountsSortedByDuration), + duration: + getDurations( + data.groupDurationsSortedByDuration, + data.groupDurationsApproximateSortedByDuration), + }).map(({group, count, duration}) => + language.encapsulate(capsule, 'item', capsule => + html.tag('li', + html.tag('div', {class: 'group-contributions-row'}, [ + group, + html.tag('span', {class: 'group-contributions-metrics'}, + // Count details are always available, since they're just the + // number of contributions directly. And duration details are + // guaranteed for every item when sorting by duration. + (slots.showBothColumns + ? language.$(capsule, 'durationCountAccent', {duration, count}) + : language.$(capsule, 'durationAccent', {duration}))), + ]))))))), + ]); + }), }; |