« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things/composite.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things/composite.js')
-rw-r--r--src/data/things/composite.js127
1 files changed, 127 insertions, 0 deletions
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.