From a92ccfe08f8ae80caab253066c8656b9bdeae88c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 26 Jan 2026 16:11:03 -0400 Subject: data: stub {Musical,Album,Track}ArtistContribution --- src/data/things/Contribution.js | 314 --------------------- src/data/things/Track.js | 2 + src/data/things/album/Album.js | 5 + src/data/things/contrib/AlbumArtistContribution.js | 12 + src/data/things/contrib/Contribution.js | 314 +++++++++++++++++++++ .../things/contrib/MusicalArtistContribution.js | 12 + src/data/things/contrib/TrackArtistContribution.js | 12 + src/data/things/contrib/index.js | 5 + src/data/things/index.js | 2 +- 9 files changed, 363 insertions(+), 315 deletions(-) delete mode 100644 src/data/things/Contribution.js create mode 100644 src/data/things/contrib/AlbumArtistContribution.js create mode 100644 src/data/things/contrib/Contribution.js create mode 100644 src/data/things/contrib/MusicalArtistContribution.js create mode 100644 src/data/things/contrib/TrackArtistContribution.js create mode 100644 src/data/things/contrib/index.js (limited to 'src/data/things') diff --git a/src/data/things/Contribution.js b/src/data/things/Contribution.js deleted file mode 100644 index 4048709b..00000000 --- a/src/data/things/Contribution.js +++ /dev/null @@ -1,314 +0,0 @@ -import {inspect} from 'node:util'; - -import CacheableObject from '#cacheable-object'; -import {colors} from '#cli'; -import {input, V} from '#composite'; -import {empty} from '#sugar'; -import Thing from '#thing'; -import {isBoolean, isStringNonEmpty, isThing} from '#validators'; - -import {simpleDate, singleReference, soupyFind} - from '#composite/wiki-properties'; - -import { - exitWithoutDependency, - exposeConstant, - exposeDependency, - exposeDependencyOrContinue, - exposeUpdateValueOrContinue, -} from '#composite/control-flow'; - -import { - withFilteredList, - withNearbyItemFromList, - withPropertyFromList, - withPropertyFromObject, -} from '#composite/data'; - -import { - inheritFromContributionPresets, - withContainingReverseContributionList, - withContributionContext, -} from '#composite/things/contribution'; - -export class Contribution extends Thing { - static [Thing.getPropertyDescriptors] = () => ({ - // Update & expose - - thing: { - flags: {update: true, expose: true}, - update: {validate: isThing}, - }, - - thingProperty: { - flags: {update: true, expose: true}, - update: {validate: isStringNonEmpty}, - }, - - artistProperty: { - flags: {update: true, expose: true}, - update: {validate: isStringNonEmpty}, - }, - - date: simpleDate(), - - artist: singleReference({ - find: soupyFind.input('artist'), - }), - - annotation: { - flags: {update: true, expose: true}, - update: {validate: isStringNonEmpty}, - }, - - countInContributionTotals: [ - exposeUpdateValueOrContinue({ - validate: input.value(isBoolean), - }), - - inheritFromContributionPresets(), - - { - dependencies: ['thing', input.myself()], - compute: (continuation, { - ['thing']: thing, - [input.myself()]: contribution, - }) => - (thing.countOwnContributionInContributionTotals?.(contribution) - ? true - : thing.countOwnContributionInContributionTotals - ? false - : continuation()), - }, - - exposeConstant(V(true)), - ], - - countInDurationTotals: [ - exposeUpdateValueOrContinue({ - validate: input.value(isBoolean), - }), - - inheritFromContributionPresets(), - - withPropertyFromObject('thing', V('duration')), - exitWithoutDependency('#thing.duration', { - value: input.value(false), - mode: input.value('falsy'), - }), - - { - dependencies: ['thing', input.myself()], - compute: (continuation, { - ['thing']: thing, - [input.myself()]: contribution, - }) => - (thing.countOwnContributionInDurationTotals?.(contribution) - ? true - : thing.countOwnContributionInDurationTotals - ? false - : continuation()), - }, - - exposeConstant(V(true)), - ], - - // Update only - - find: soupyFind(), - - // Expose only - - isContribution: exposeConstant(V(true)), - - annotationParts: { - flags: {expose: true}, - expose: { - dependencies: ['annotation'], - compute: ({annotation}) => - (annotation - ? annotation.split(',').map(part => part.trim()) - : []), - }, - }, - - context: [ - withContributionContext(), - - { - dependencies: [ - '#contributionTarget', - '#contributionProperty', - ], - - compute: ({ - ['#contributionTarget']: target, - ['#contributionProperty']: property, - }) => ({ - target, - property, - }), - }, - ], - - matchingPresets: [ - withPropertyFromObject('thing', { - property: input.value('wikiInfo'), - internal: input.value(true), - }), - - exitWithoutDependency('#thing.wikiInfo', V([])), - - withPropertyFromObject('#thing.wikiInfo', V('contributionPresets')) - .outputs({'#thing.wikiInfo.contributionPresets': '#contributionPresets'}), - - exitWithoutDependency('#contributionPresets', V([]), V('empty')), - - withContributionContext(), - - // TODO: implementing this with compositional filters would be fun - { - dependencies: [ - '#contributionPresets', - '#contributionTarget', - '#contributionProperty', - 'annotation', - ], - - compute: ({ - ['#contributionPresets']: presets, - ['#contributionTarget']: target, - ['#contributionProperty']: property, - ['annotation']: annotation, - }) => - presets.filter(preset => - preset.context[0] === target && - preset.context.slice(1).includes(property) && - // For now, only match if the annotation is a complete match. - // Partial matches (e.g. because the contribution includes "two" - // annotations, separated by commas) don't count. - preset.annotation === annotation), - }, - ], - - // All the contributions from the list which includes this contribution. - // Note that this list contains not only other contributions by the same - // artist, but also this very contribution. It doesn't mix contributions - // exposed on different properties. - associatedContributions: [ - exitWithoutDependency('thing', V([])), - exitWithoutDependency('thingProperty', V([])), - - withPropertyFromObject('thing', 'thingProperty') - .outputs({'#value': '#contributions'}), - - withPropertyFromList('#contributions', V('annotation')), - - { - dependencies: ['#contributions.annotation', 'annotation'], - compute: (continuation, { - ['#contributions.annotation']: contributionAnnotations, - ['annotation']: annotation, - }) => continuation({ - ['#likeContributionsFilter']: - contributionAnnotations.map(mappingAnnotation => - (annotation?.startsWith(`edits for wiki`) - ? mappingAnnotation?.startsWith(`edits for wiki`) - : !mappingAnnotation?.startsWith(`edits for wiki`))), - }), - }, - - withFilteredList('#contributions', '#likeContributionsFilter') - .outputs({'#filteredList': '#contributions'}), - - exposeDependency('#contributions'), - ], - - previousBySameArtist: [ - withContainingReverseContributionList() - .outputs({'#containingReverseContributionList': '#list'}), - - exitWithoutDependency('#list'), - - withNearbyItemFromList('#list', input.myself(), V(-1)), - exposeDependency('#nearbyItem'), - ], - - nextBySameArtist: [ - withContainingReverseContributionList() - .outputs({'#containingReverseContributionList': '#list'}), - - exitWithoutDependency('#list'), - - withNearbyItemFromList('#list', input.myself(), V(+1)), - exposeDependency('#nearbyItem'), - ], - - groups: [ - withPropertyFromObject('thing', V('groups')), - exposeDependencyOrContinue('#thing.groups'), - - exposeConstant(V([])), - ], - }); - - [inspect.custom](depth, options, inspect) { - const parts = []; - const accentParts = []; - - parts.push(Thing.prototype[inspect.custom].apply(this)); - - if (this.annotation) { - accentParts.push(colors.green(`"${this.annotation}"`)); - } - - if (this.date) { - accentParts.push(colors.yellow(this.date.toLocaleDateString())); - } - - let artistRef; - if (depth >= 0) { - let artist; - try { - artist = this.artist; - } catch { - // Computing artist might crash for any reason - don't distract from - // other errors as a result of inspecting this contribution. - } - - if (artist) { - artistRef = - colors.blue(Thing.getReference(artist)); - } - } else { - artistRef = - colors.green(CacheableObject.getUpdateValue(this, 'artist')); - } - - if (artistRef) { - accentParts.push(`by ${artistRef}`); - } - - if (this.thing) { - if (depth >= 0) { - const newOptions = { - ...options, - depth: - (options.depth === null - ? null - : options.depth - 1), - }; - - accentParts.push(`to ${inspect(this.thing, newOptions)}`); - } else { - accentParts.push(`to ${colors.blue(Thing.getReference(this.thing))}`); - } - } - - if (!empty(accentParts)) { - parts.push(` (${accentParts.join(', ')})`); - } - - return parts.join(''); - } -} diff --git a/src/data/things/Track.js b/src/data/things/Track.js index 57ff3a94..5f825509 100644 --- a/src/data/things/Track.js +++ b/src/data/things/Track.js @@ -116,6 +116,7 @@ export class Track extends Thing { LyricsEntry, MusicVideo, ReferencingSourcesEntry, + TrackTrackArtistContribution, TrackSection, WikiInfo, }) => ({ @@ -262,6 +263,7 @@ export class Track extends Thing { artistContribs: [ withResolvedContribs({ from: input.updateValue({validate: isContributionList}), + class: input.value(TrackTrackArtistContribution), date: 'date', thingProperty: input.thisProperty(), artistProperty: input.value('trackArtistContributions'), diff --git a/src/data/things/album/Album.js b/src/data/things/album/Album.js index e81615d4..ebec5444 100644 --- a/src/data/things/album/Album.js +++ b/src/data/things/album/Album.js @@ -68,11 +68,13 @@ export class Album extends Thing { static [Thing.getPropertyDescriptors] = ({ AdditionalFile, AdditionalName, + AlbumArtistContribution, ArtTag, Artwork, CommentaryEntry, CreditingSourcesEntry, Group, + TrackArtistContribution, TrackSection, WikiInfo, }) => ({ @@ -119,6 +121,7 @@ export class Album extends Thing { // > Update & expose - Credits and contributors artistContribs: contributionList({ + class: input.value(AlbumArtistContribution), artistProperty: input.value('albumArtistContributions'), }), @@ -127,6 +130,7 @@ export class Album extends Thing { trackArtistContribs: [ withResolvedContribs({ from: input.updateValue({validate: isContributionList}), + class: input.value(TrackArtistContribution), thingProperty: input.thisProperty(), artistProperty: input.value('albumTrackArtistContributions'), }).outputs({ @@ -136,6 +140,7 @@ export class Album extends Thing { exposeDependencyOrContinue('#trackArtistContribs', V('empty')), withRecontextualizedContributionList('artistContribs', { + reclass: input.value(TrackArtistContribution), artistProperty: input.value('albumTrackArtistContributions'), }), diff --git a/src/data/things/contrib/AlbumArtistContribution.js b/src/data/things/contrib/AlbumArtistContribution.js new file mode 100644 index 00000000..7b6bc9da --- /dev/null +++ b/src/data/things/contrib/AlbumArtistContribution.js @@ -0,0 +1,12 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {MusicalArtistContribution} from './MusicalArtistContribution.js'; + +export class AlbumArtistContribution extends MusicalArtistContribution { + static [Thing.getPropertyDescriptors] = () => ({ + isAlbumArtistContribution: exposeConstant(V(true)), + }); +} diff --git a/src/data/things/contrib/Contribution.js b/src/data/things/contrib/Contribution.js new file mode 100644 index 00000000..4048709b --- /dev/null +++ b/src/data/things/contrib/Contribution.js @@ -0,0 +1,314 @@ +import {inspect} from 'node:util'; + +import CacheableObject from '#cacheable-object'; +import {colors} from '#cli'; +import {input, V} from '#composite'; +import {empty} from '#sugar'; +import Thing from '#thing'; +import {isBoolean, isStringNonEmpty, isThing} from '#validators'; + +import {simpleDate, singleReference, soupyFind} + from '#composite/wiki-properties'; + +import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import { + withFilteredList, + withNearbyItemFromList, + withPropertyFromList, + withPropertyFromObject, +} from '#composite/data'; + +import { + inheritFromContributionPresets, + withContainingReverseContributionList, + withContributionContext, +} from '#composite/things/contribution'; + +export class Contribution extends Thing { + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + thing: { + flags: {update: true, expose: true}, + update: {validate: isThing}, + }, + + thingProperty: { + flags: {update: true, expose: true}, + update: {validate: isStringNonEmpty}, + }, + + artistProperty: { + flags: {update: true, expose: true}, + update: {validate: isStringNonEmpty}, + }, + + date: simpleDate(), + + artist: singleReference({ + find: soupyFind.input('artist'), + }), + + annotation: { + flags: {update: true, expose: true}, + update: {validate: isStringNonEmpty}, + }, + + countInContributionTotals: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + inheritFromContributionPresets(), + + { + dependencies: ['thing', input.myself()], + compute: (continuation, { + ['thing']: thing, + [input.myself()]: contribution, + }) => + (thing.countOwnContributionInContributionTotals?.(contribution) + ? true + : thing.countOwnContributionInContributionTotals + ? false + : continuation()), + }, + + exposeConstant(V(true)), + ], + + countInDurationTotals: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + inheritFromContributionPresets(), + + withPropertyFromObject('thing', V('duration')), + exitWithoutDependency('#thing.duration', { + value: input.value(false), + mode: input.value('falsy'), + }), + + { + dependencies: ['thing', input.myself()], + compute: (continuation, { + ['thing']: thing, + [input.myself()]: contribution, + }) => + (thing.countOwnContributionInDurationTotals?.(contribution) + ? true + : thing.countOwnContributionInDurationTotals + ? false + : continuation()), + }, + + exposeConstant(V(true)), + ], + + // Update only + + find: soupyFind(), + + // Expose only + + isContribution: exposeConstant(V(true)), + + annotationParts: { + flags: {expose: true}, + expose: { + dependencies: ['annotation'], + compute: ({annotation}) => + (annotation + ? annotation.split(',').map(part => part.trim()) + : []), + }, + }, + + context: [ + withContributionContext(), + + { + dependencies: [ + '#contributionTarget', + '#contributionProperty', + ], + + compute: ({ + ['#contributionTarget']: target, + ['#contributionProperty']: property, + }) => ({ + target, + property, + }), + }, + ], + + matchingPresets: [ + withPropertyFromObject('thing', { + property: input.value('wikiInfo'), + internal: input.value(true), + }), + + exitWithoutDependency('#thing.wikiInfo', V([])), + + withPropertyFromObject('#thing.wikiInfo', V('contributionPresets')) + .outputs({'#thing.wikiInfo.contributionPresets': '#contributionPresets'}), + + exitWithoutDependency('#contributionPresets', V([]), V('empty')), + + withContributionContext(), + + // TODO: implementing this with compositional filters would be fun + { + dependencies: [ + '#contributionPresets', + '#contributionTarget', + '#contributionProperty', + 'annotation', + ], + + compute: ({ + ['#contributionPresets']: presets, + ['#contributionTarget']: target, + ['#contributionProperty']: property, + ['annotation']: annotation, + }) => + presets.filter(preset => + preset.context[0] === target && + preset.context.slice(1).includes(property) && + // For now, only match if the annotation is a complete match. + // Partial matches (e.g. because the contribution includes "two" + // annotations, separated by commas) don't count. + preset.annotation === annotation), + }, + ], + + // All the contributions from the list which includes this contribution. + // Note that this list contains not only other contributions by the same + // artist, but also this very contribution. It doesn't mix contributions + // exposed on different properties. + associatedContributions: [ + exitWithoutDependency('thing', V([])), + exitWithoutDependency('thingProperty', V([])), + + withPropertyFromObject('thing', 'thingProperty') + .outputs({'#value': '#contributions'}), + + withPropertyFromList('#contributions', V('annotation')), + + { + dependencies: ['#contributions.annotation', 'annotation'], + compute: (continuation, { + ['#contributions.annotation']: contributionAnnotations, + ['annotation']: annotation, + }) => continuation({ + ['#likeContributionsFilter']: + contributionAnnotations.map(mappingAnnotation => + (annotation?.startsWith(`edits for wiki`) + ? mappingAnnotation?.startsWith(`edits for wiki`) + : !mappingAnnotation?.startsWith(`edits for wiki`))), + }), + }, + + withFilteredList('#contributions', '#likeContributionsFilter') + .outputs({'#filteredList': '#contributions'}), + + exposeDependency('#contributions'), + ], + + previousBySameArtist: [ + withContainingReverseContributionList() + .outputs({'#containingReverseContributionList': '#list'}), + + exitWithoutDependency('#list'), + + withNearbyItemFromList('#list', input.myself(), V(-1)), + exposeDependency('#nearbyItem'), + ], + + nextBySameArtist: [ + withContainingReverseContributionList() + .outputs({'#containingReverseContributionList': '#list'}), + + exitWithoutDependency('#list'), + + withNearbyItemFromList('#list', input.myself(), V(+1)), + exposeDependency('#nearbyItem'), + ], + + groups: [ + withPropertyFromObject('thing', V('groups')), + exposeDependencyOrContinue('#thing.groups'), + + exposeConstant(V([])), + ], + }); + + [inspect.custom](depth, options, inspect) { + const parts = []; + const accentParts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (this.annotation) { + accentParts.push(colors.green(`"${this.annotation}"`)); + } + + if (this.date) { + accentParts.push(colors.yellow(this.date.toLocaleDateString())); + } + + let artistRef; + if (depth >= 0) { + let artist; + try { + artist = this.artist; + } catch { + // Computing artist might crash for any reason - don't distract from + // other errors as a result of inspecting this contribution. + } + + if (artist) { + artistRef = + colors.blue(Thing.getReference(artist)); + } + } else { + artistRef = + colors.green(CacheableObject.getUpdateValue(this, 'artist')); + } + + if (artistRef) { + accentParts.push(`by ${artistRef}`); + } + + if (this.thing) { + if (depth >= 0) { + const newOptions = { + ...options, + depth: + (options.depth === null + ? null + : options.depth - 1), + }; + + accentParts.push(`to ${inspect(this.thing, newOptions)}`); + } else { + accentParts.push(`to ${colors.blue(Thing.getReference(this.thing))}`); + } + } + + if (!empty(accentParts)) { + parts.push(` (${accentParts.join(', ')})`); + } + + return parts.join(''); + } +} diff --git a/src/data/things/contrib/MusicalArtistContribution.js b/src/data/things/contrib/MusicalArtistContribution.js new file mode 100644 index 00000000..b4f8d370 --- /dev/null +++ b/src/data/things/contrib/MusicalArtistContribution.js @@ -0,0 +1,12 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {Contribution} from './Contribution.js'; + +export class MusicalArtistContribution extends Contribution { + static [Thing.getPropertyDescriptors] = () => ({ + isMusicalArtistContribution: exposeConstant(V(true)), + }); +} diff --git a/src/data/things/contrib/TrackArtistContribution.js b/src/data/things/contrib/TrackArtistContribution.js new file mode 100644 index 00000000..ecbe9b34 --- /dev/null +++ b/src/data/things/contrib/TrackArtistContribution.js @@ -0,0 +1,12 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {MusicalArtistContribution} from './MusicalArtistContribution.js'; + +export class TrackArtistContribution extends MusicalArtistContribution { + static [Thing.getPropertyDescriptors] = () => ({ + isTrackArtistContribution: exposeConstant(V(true)), + }); +} diff --git a/src/data/things/contrib/index.js b/src/data/things/contrib/index.js new file mode 100644 index 00000000..a0629bdf --- /dev/null +++ b/src/data/things/contrib/index.js @@ -0,0 +1,5 @@ +export * from './Contribution.js'; + +export * from './MusicalArtistContribution.js'; +export * from './AlbumArtistContribution.js'; +export * from './TrackArtistContribution.js'; diff --git a/src/data/things/index.js b/src/data/things/index.js index bf8a5a33..3773864b 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -2,6 +2,7 @@ export * from './album/index.js'; export * from './content/index.js'; +export * from './contrib/index.js'; export * from './flash/index.js'; export * from './group/index.js'; export * from './homepage-layout/index.js'; @@ -12,7 +13,6 @@ export * from './AdditionalName.js'; export * from './ArtTag.js'; export * from './Artist.js'; export * from './Artwork.js'; -export * from './Contribution.js'; export * from './Language.js'; export * from './MusicVideo.js'; export * from './NewsEntry.js'; -- cgit 1.3.0-6-gf8a5