diff options
Diffstat (limited to 'src/data/things/artwork.js')
| -rw-r--r-- | src/data/things/artwork.js | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/data/things/artwork.js b/src/data/things/artwork.js new file mode 100644 index 00000000..c1aafa8f --- /dev/null +++ b/src/data/things/artwork.js @@ -0,0 +1,422 @@ +import {inspect} from 'node:util'; + +import {colors} from '#cli'; +import {input, V} from '#composite'; +import find from '#find'; +import Thing from '#thing'; + +import { + isContentString, + isContributionList, + isDate, + isDimensions, + isFileExtension, + optional, + validateArrayItems, + validateProperties, + validateReference, + validateReferenceList, +} from '#validators'; + +import { + parseAnnotatedReferences, + parseContributors, + parseDate, + parseDimensions, +} from '#yaml'; + +import { + exitWithoutDependency, + exposeConstant, + exposeDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, + flipFilter, +} from '#composite/control-flow'; + +import { + withFilteredList, + withNearbyItemFromList, + withPropertyFromList, + withPropertyFromObject, +} from '#composite/data'; + +import { + constituteFrom, + constituteOrContinue, + withRecontextualizedContributionList, + withResolvedAnnotatedReferenceList, + withResolvedContribs, + withResolvedReferenceList, +} from '#composite/wiki-data'; + +import { + contentString, + directory, + flag, + reverseReferenceList, + simpleString, + soupyFind, + soupyReverse, + thing, + wikiData, +} from '#composite/wiki-properties'; + +import {withContainingArtworkList} from '#composite/things/artwork'; + +export class Artwork extends Thing { + static [Thing.referenceType] = 'artwork'; + static [Thing.wikiData] = 'artworkData'; + + static [Thing.constitutibleProperties] = [ + // Contributions currently aren't being observed for constitution. + // 'artistContribs', // from attached artwork or thing + ]; + + static [Thing.getPropertyDescriptors] = ({ArtTag}) => ({ + // Update & expose + + unqualifiedDirectory: directory({ + name: input.value(null), + }), + + thing: thing(), + thingProperty: simpleString(), + + label: simpleString(), + source: contentString(), + originDetails: contentString(), + showFilename: simpleString(), + + dateFromThingProperty: simpleString(), + + date: [ + exposeUpdateValueOrContinue({ + validate: input.value(isDate), + }), + + constituteFrom('thing', 'dateFromThingProperty'), + ], + + fileExtensionFromThingProperty: simpleString(), + + fileExtension: [ + exposeUpdateValueOrContinue({ + validate: input.value(isFileExtension), + }), + + constituteFrom('thing', 'fileExtensionFromThingProperty', { + else: input.value('jpg'), + }), + ], + + dimensionsFromThingProperty: simpleString(), + + dimensions: [ + exposeUpdateValueOrContinue({ + validate: input.value(isDimensions), + }), + + constituteFrom('thing', 'dimensionsFromThingProperty'), + ], + + attachAbove: flag(V(false)), + + artistContribsFromThingProperty: simpleString(), + artistContribsArtistProperty: simpleString(), + + artistContribs: [ + withResolvedContribs({ + from: input.updateValue({validate: isContributionList}), + date: 'date', + thingProperty: input.thisProperty(), + artistProperty: 'artistContribsArtistProperty', + }), + + exposeDependencyOrContinue('#resolvedContribs', V('empty')), + + withPropertyFromObject('attachedArtwork', V('artistContribs')), + + withRecontextualizedContributionList('#attachedArtwork.artistContribs'), + exposeDependencyOrContinue('#attachedArtwork.artistContribs'), + + exitWithoutDependency('artistContribsFromThingProperty', V([])), + + withPropertyFromObject('thing', 'artistContribsFromThingProperty') + .outputs({'#value': '#artistContribsFromThing'}), + + withRecontextualizedContributionList('#artistContribsFromThing'), + exposeDependency('#artistContribsFromThing'), + ], + + style: simpleString(), + + artTagsFromThingProperty: simpleString(), + + artTags: [ + withResolvedReferenceList({ + list: input.updateValue({ + validate: + validateReferenceList(ArtTag[Thing.referenceType]), + }), + find: soupyFind.input('artTag'), + }), + + exposeDependencyOrContinue('#resolvedReferenceList', V('empty')), + + constituteOrContinue('attachedArtwork', V('artTags'), V('empty')), + + constituteFrom('thing', 'artTagsFromThingProperty', V([])), + ], + + referencedArtworksFromThingProperty: simpleString(), + + referencedArtworks: [ + { + compute: (continuation) => continuation({ + ['#find']: + find.mixed({ + track: find.trackPrimaryArtwork, + album: find.albumPrimaryArtwork, + }), + }), + }, + + withResolvedAnnotatedReferenceList({ + list: input.updateValue({ + validate: + // TODO: It's annoying to hardcode this when it's really the + // same behavior as through annotatedReferenceList and through + // referenceListUpdateDescription, the latter of which isn't + // available outside of #composite/wiki-data internals. + validateArrayItems( + validateProperties({ + reference: validateReference(['album', 'track']), + annotation: optional(isContentString), + })), + }), + + data: '_artworkData', + find: '#find', + + thing: input.value('artwork'), + }), + + exposeDependencyOrContinue('#resolvedAnnotatedReferenceList', V('empty')), + + constituteFrom('thing', 'referencedArtworksFromThingProperty', { + else: input.value([]), + }), + ], + + // Update only + + find: soupyFind(), + reverse: soupyReverse(), + + // used for referencedArtworks (mixedFind) + artworkData: wikiData(V(Artwork)), + + // Expose only + + isArtwork: exposeConstant(V(true)), + + referencedByArtworks: reverseReferenceList({ + reverse: soupyReverse.input('artworksWhichReference'), + }), + + isMainArtwork: [ + withContainingArtworkList(), + exitWithoutDependency('#containingArtworkList'), + + { + dependencies: [input.myself(), '#containingArtworkList'], + compute: ({ + [input.myself()]: myself, + ['#containingArtworkList']: list, + }) => + list[0] === myself, + }, + ], + + mainArtwork: [ + withContainingArtworkList(), + exitWithoutDependency('#containingArtworkList'), + + { + dependencies: ['#containingArtworkList'], + compute: ({'#containingArtworkList': list}) => + list[0], + }, + ], + + attachedArtwork: [ + exitWithoutDependency('attachAbove', { + value: input.value(null), + mode: input.value('falsy'), + }), + + withContainingArtworkList(), + + withPropertyFromList('#containingArtworkList', V('attachAbove')), + + flipFilter('#containingArtworkList.attachAbove') + .outputs({'#containingArtworkList.attachAbove': '#filterNotAttached'}), + + withNearbyItemFromList({ + list: '#containingArtworkList', + item: input.myself(), + offset: input.value(-1), + filter: '#filterNotAttached', + }), + + exposeDependency('#nearbyItem'), + ], + + attachingArtworks: reverseReferenceList({ + reverse: soupyReverse.input('artworksWhichAttach'), + }), + + groups: [ + withPropertyFromObject('thing', V('groups')), + exposeDependencyOrContinue('#thing.groups'), + + exposeConstant(V([])), + ], + + contentWarningArtTags: [ + withPropertyFromList('artTags', V('isContentWarning')), + withFilteredList('artTags', '#artTags.isContentWarning'), + exposeDependency('#filteredList'), + ], + + contentWarnings: [ + withPropertyFromList('contentWarningArtTags', V('name')), + exposeDependency('#contentWarningArtTags.name'), + ], + + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Directory': {property: 'unqualifiedDirectory'}, + 'File Extension': {property: 'fileExtension'}, + + 'Dimensions': { + property: 'dimensions', + transform: parseDimensions, + }, + + 'Label': {property: 'label'}, + 'Source': {property: 'source'}, + 'Origin Details': {property: 'originDetails'}, + 'Show Filename': {property: 'showFilename'}, + + 'Date': { + property: 'date', + transform: parseDate, + }, + + 'Attach Above': {property: 'attachAbove'}, + + 'Artists': { + property: 'artistContribs', + transform: parseContributors, + }, + + 'Style': {property: 'style'}, + + 'Tags': {property: 'artTags'}, + + 'Referenced Artworks': { + property: 'referencedArtworks', + transform: parseAnnotatedReferences, + }, + }, + }; + + static [Thing.reverseSpecs] = { + artworksWhichReference: { + bindTo: 'artworkData', + + referencing: referencingArtwork => + referencingArtwork.referencedArtworks + .map(({artwork: referencedArtwork, ...referenceDetails}) => ({ + referencingArtwork, + referencedArtwork, + referenceDetails, + })), + + referenced: ({referencedArtwork}) => [referencedArtwork], + + tidy: ({referencingArtwork, referenceDetails}) => ({ + artwork: referencingArtwork, + ...referenceDetails, + }), + + date: ({artwork}) => artwork.date, + }, + + artworksWhichAttach: { + bindTo: 'artworkData', + + referencing: referencingArtwork => + (referencingArtwork.attachAbove + ? [referencingArtwork] + : []), + + referenced: referencingArtwork => + [referencingArtwork.attachedArtwork], + }, + + artworksWhichFeature: { + bindTo: 'artworkData', + + referencing: artwork => [artwork], + referenced: artwork => artwork.artTags, + }, + }; + + get path() { + if (!this.thing) return null; + if (!this.thing.getOwnArtworkPath) return null; + + return this.thing.getOwnArtworkPath(this); + } + + countOwnContributionInContributionTotals(contrib) { + if (this.attachAbove) { + return false; + } + + if (contrib.annotation?.startsWith('edits for wiki')) { + return false; + } + + return true; + } + + [inspect.custom](depth, options, inspect) { + const parts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (this.thing) { + if (depth >= 0) { + const newOptions = { + ...options, + depth: + (options.depth === null + ? null + : options.depth - 1), + }; + + parts.push(` for ${inspect(this.thing, newOptions)}`); + } else { + parts.push(` for ${colors.blue(Thing.getReference(this.thing))}`); + } + } + + return parts.join(''); + } +} |