From c82784ebb4e5141bfe97664f3252303b3e833863 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 9 Sep 2023 08:13:44 -0300 Subject: data: withPropertyFrom{Object,List}, fillMissingListItems utils --- src/data/things/album.js | 66 +++++++++------------- src/data/things/composite.js | 127 +++++++++++++++++++++++++++++++++++++++++++ src/data/things/thing.js | 11 +--- src/data/things/track.js | 16 +----- 4 files changed, 159 insertions(+), 61 deletions(-) diff --git a/src/data/things/album.js b/src/data/things/album.js index 5e281f06..288caa04 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -8,8 +8,11 @@ import { exitWithoutUpdateValue, exposeDependency, exposeUpdateValueOrContinue, + fillMissingListItems, withFlattenedArray, + withPropertyFromList, withUnflattenedArray, + withUpdateValueAsDependency, } from '#composite'; import Thing, { @@ -81,50 +84,35 @@ export class Album extends Thing { exitWithoutDependency({dependency: 'trackData', value: []}), exitWithoutUpdateValue({value: [], mode: 'empty'}), - { - transform: (trackSections, continuation) => - continuation(trackSections, { - '#sectionTrackRefs': - trackSections.map(section => section.tracks), - - '#sectionDateOriginallyReleased': - trackSections - .map(({dateOriginallyReleased}) => dateOriginallyReleased ?? null), - - '#sectionIsDefaultTrackSection': - trackSections - .map(({isDefaultTrackSection}) => isDefaultTrackSection ?? false), - }), - }, + withUpdateValueAsDependency({into: '#sections'}), - { - dependencies: ['color'], - transform: (trackSections, {color: albumColor}, continuation) => - continuation(trackSections, { - '#sectionColor': - trackSections - .map(({color: sectionColor}) => sectionColor ?? albumColor), - }), - }, + withPropertyFromList({list: '#sections', property: 'tracks', into: '#sections.trackRefs'}), + withPropertyFromList({list: '#sections', property: 'dateOriginallyReleased'}), + withPropertyFromList({list: '#sections', property: 'isDefaultTrackSection'}), + withPropertyFromList({list: '#sections', property: 'color'}), + + fillMissingListItems({list: '#sections.trackRefs', value: []}), + fillMissingListItems({list: '#sections.isDefaultTrackSection', value: false}), + fillMissingListItems({list: '#sections.color', dependency: 'color'}), withFlattenedArray({ - from: '#sectionTrackRefs', + from: '#sections.trackRefs', into: '#trackRefs', - intoIndices: '#sectionStartIndex', + intoIndices: '#sections.startIndex', }), withResolvedReferenceList({ list: '#trackRefs', data: 'trackData', - mode: 'null', + notFoundMode: 'null', find: find.track, into: '#tracks', }), withUnflattenedArray({ from: '#tracks', - fromIndices: '#sectionStartIndex', - into: '#sectionTracks', + fromIndices: '#sections.startIndex', + into: '#sections.tracks', }), { @@ -134,19 +122,19 @@ export class Album extends Thing { expose: { dependencies: [ - '#sectionTracks', - '#sectionColor', - '#sectionDateOriginallyReleased', - '#sectionIsDefaultTrackSection', - '#sectionStartIndex', + '#sections.tracks', + '#sections.color', + '#sections.dateOriginallyReleased', + '#sections.isDefaultTrackSection', + '#sections.startIndex', ], transform: (trackSections, { - '#sectionTracks': tracks, - '#sectionColor': color, - '#sectionDateOriginallyReleased': dateOriginallyReleased, - '#sectionIsDefaultTrackSection': isDefaultTrackSection, - '#sectionStartIndex': startIndex, + '#sections.tracks': tracks, + '#sections.color': color, + '#sections.dateOriginallyReleased': dateOriginallyReleased, + '#sections.isDefaultTrackSection': isDefaultTrackSection, + '#sections.startIndex': startIndex, }) => stitchArrays({ tracks, diff --git a/src/data/things/composite.js b/src/data/things/composite.js index 1f6482f6..a5adc3e9 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -1089,6 +1089,133 @@ export function withUpdateValueAsDependency({ }; } +// Gets a property of some object (in a dependency) and provides that value. +// If the object itself is null, the provided dependency will also always be +// null. By default, it'll also be null if the object doesn't have the listed +// property (or its value is undefined/null); provide a value on {missing} to +// default to something else here. +export function withPropertyFromObject({ + object, + property, + into = null, +}) { + into ??= + (object.startsWith('#') + ? `${object}.${property}` + : `#${object}.${property}`); + + return { + annotation: `withPropertyFromObject`, + flags: {expose: true, compose: true}, + + expose: { + mapDependencies: {object}, + mapContinuation: {into}, + options: {property}, + + compute({object, '#options': {property}}, continuation) { + if (object === null || object === undefined) return continuation({into: null}); + if (!Object.hasOwn(object, property)) return continuation({into: null}); + return continuation({into: object[property] ?? null}); + }, + }, + }; +} + +// Gets a property from each of a list of objects (in a dependency) and +// provides the results. This doesn't alter any list indices, so positions +// which were null in the original list are kept null here. Objects which don't +// have the specified property are also included in-place as null, by default; +// provide a value on {missing} to default to something else here. +export function withPropertyFromList({ + list, + property, + into = null, + missing = null, +}) { + into ??= + (list.startsWith('#') + ? `${list}.${property}` + : `#${list}.${property}`); + + return { + annotation: `withPropertyFromList`, + flags: {expose: true, compose: true}, + + expose: { + mapDependencies: {list}, + mapContinuation: {into}, + options: {property}, + + compute({list, '#options': {property}}, continuation) { + if (list === undefined || empty(list)) { + return continuation({into: []}); + } + + return continuation({ + into: + list.map(item => { + if (item === null || item === undefined) return null; + if (!Object.hasOwn(item, property)) return missing; + return item[property] ?? missing; + }), + }); + }, + }, + }; +} + +// Replaces items of a list, which are null or undefined, with some fallback +// value, either a constant (set {value}) or from a dependency ({dependency}). +// By default, this replaces the passed dependency. +export function fillMissingListItems({ + list, + value, + dependency, + into = list, +}) { + if (value !== undefined && dependency !== undefined) { + throw new TypeError(`Don't provide both value and dependency`); + } + + if (value === undefined && dependency === undefined) { + throw new TypeError(`Missing value or dependency`); + } + + if (dependency) { + return { + annotation: `fillMissingListItems.fromDependency`, + flags: {expose: true, compose: true}, + + expose: { + mapDependencies: {list, dependency}, + mapContinuation: {into}, + + compute: ({list, dependency}, continuation) => + continuation({ + into: list.map(item => item ?? dependency), + }), + }, + }; + } else { + return { + annotation: `fillMissingListItems.fromValue`, + flags: {expose: true, compose: true}, + + expose: { + mapDependencies: {list}, + mapContinuation: {into}, + options: {value}, + + compute: ({list, '#options': {value}}, continuation) => + continuation({ + into: list.map(item => item ?? value), + }), + }, + }; + } +} + // Flattens an array with one level of nested arrays, providing as dependencies // both the flattened array as well as the original starting indices of each // successive source array. diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 96ac9b12..a87e6ed6 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -15,6 +15,7 @@ import { exposeDependency, exposeDependencyOrContinue, raiseWithoutDependency, + withPropertyFromList, withUpdateValueAsDependency, } from '#composite'; @@ -408,14 +409,8 @@ export function withResolvedContribs({ raise: {into: []}, }), - { - mapDependencies: {from}, - compute: ({from}, continuation) => - continuation({ - '#artistRefs': from.map(({who}) => who), - '#what': from.map(({what}) => what), - }), - }, + withPropertyFromList({list: from, property: 'who', into: '#artistRefs'}), + withPropertyFromList({list: from, property: 'what', into: '#what'}), withResolvedReferenceList({ list: '#artistRefs', diff --git a/src/data/things/track.js b/src/data/things/track.js index 53798cda..a307fda9 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -11,6 +11,7 @@ import { exposeDependency, exposeDependencyOrContinue, exposeUpdateValueOrContinue, + withPropertyFromObject, withResultOfAvailabilityCheck, withUpdateValueAsDependency, } from '#composite'; @@ -430,20 +431,7 @@ function withAlbumProperty({ }) { return compositeFrom(`withAlbumProperty`, [ withAlbum({notFoundMode}), - - { - dependencies: ['#album'], - options: {property}, - mapContinuation: {into}, - - compute: ({ - '#album': album, - '#options': {property}, - }, continuation) => - (album - ? continuation.raise({into: album[property]}) - : continuation.raise({into: null})), - }, + withPropertyFromObject({object: '#album', property, into}), ]); } -- cgit 1.3.0-6-gf8a5