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/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 + 5 files changed, 355 insertions(+) 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/contrib') 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'; -- cgit 1.3.0-6-gf8a5