« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/thing
diff options
context:
space:
mode:
Diffstat (limited to 'src/thing')
-rw-r--r--src/thing/album.js5
-rw-r--r--src/thing/cacheable-object.js6
-rw-r--r--src/thing/flash.js129
-rw-r--r--src/thing/validators.js45
4 files changed, 181 insertions, 4 deletions
diff --git a/src/thing/album.js b/src/thing/album.js
index 11af8019..8a9fde2c 100644
--- a/src/thing/album.js
+++ b/src/thing/album.js
@@ -10,6 +10,7 @@ import {
     isDate,
     isDimensions,
     isDirectory,
+    isFileExtension,
     isName,
     isURL,
     isString,
@@ -176,7 +177,7 @@ export default class Album extends Thing {
 
         wallpaperFileExtension: {
             flags: {update: true, expose: true},
-            update: {validate: isString}
+            update: {validate: isFileExtension}
         },
 
         bannerStyle: {
@@ -186,7 +187,7 @@ export default class Album extends Thing {
 
         bannerFileExtension: {
             flags: {update: true, expose: true},
-            update: {validate: isString}
+            update: {validate: isFileExtension}
         },
 
         bannerDimensions: {
diff --git a/src/thing/cacheable-object.js b/src/thing/cacheable-object.js
index f478fd23..3c14101c 100644
--- a/src/thing/cacheable-object.js
+++ b/src/thing/cacheable-object.js
@@ -214,11 +214,13 @@ export default class CacheableObject {
     #getExposeComputeFunction(property) {
         const { flags, expose } = this.#getPropertyDescriptor(property);
 
-        const compute = (!flags.update && expose?.compute);
-        const transform = (flags.update && expose?.transform);
+        const compute = expose?.compute;
+        const transform = expose?.transform;
 
         if (flags.update && !transform) {
             return null;
+        } else if (flags.update && compute) {
+            throw new Error(`Updating property ${property} has compute function, should be formatted as transform`);
         } else if (!flags.update && !compute) {
             throw new Error(`Exposed property ${property} does not update and is missing compute function`);
         }
diff --git a/src/thing/flash.js b/src/thing/flash.js
new file mode 100644
index 00000000..4eac65ad
--- /dev/null
+++ b/src/thing/flash.js
@@ -0,0 +1,129 @@
+import Thing from './thing.js';
+
+import {
+    isColor,
+    isContributionList,
+    isDate,
+    isDirectory,
+    isFileExtension,
+    isName,
+    isNumber,
+    isString,
+    isURL,
+    oneOf,
+    validateArrayItems,
+    validateReferenceList,
+} from './validators.js';
+
+export default class Flash extends Thing {
+    static [Thing.referenceType] = 'flash';
+
+    static propertyDescriptors = {
+        // Update & expose
+
+        name: {
+            flags: {update: true, expose: true},
+
+            update: {
+                default: 'Unnamed Flash',
+                validate: isName
+            }
+        },
+
+        directory: {
+            flags: {update: true, expose: true},
+            update: {validate: isDirectory},
+
+            // Flashes expose directory differently from other Things! Their
+            // default directory is dependent on the page number (or ID), not
+            // the name.
+            expose: {
+                dependencies: ['page'],
+                transform(directory, { page }) {
+                    if (directory === null && page === null)
+                        return null;
+                    else if (directory === null)
+                        return page;
+                    else
+                        return directory;
+                }
+            }
+        },
+
+        page: {
+            flags: {update: true, expose: true},
+            update: {validate: oneOf(isString, isNumber)},
+
+            expose: {
+                transform: value => value.toString()
+            }
+        },
+
+        date: {
+            flags: {update: true, expose: true},
+            update: {validate: isDate}
+        },
+
+        coverArtFileExtension: {
+            flags: {update: true, expose: true},
+            update: {validate: isFileExtension}
+        },
+
+        featuredTracksByRef: {
+            flags: {update: true, expose: true},
+            update: {validate: validateReferenceList('track')}
+        },
+
+        contributorContribsByRef: {
+            flags: {update: true, expose: true},
+            update: {validate: isContributionList}
+        },
+
+        urls: {
+            flags: {update: true, expose: true},
+            update: {validate: validateArrayItems(isURL)}
+        },
+    };
+}
+
+export class FlashAct extends Thing {
+    static [Thing.referenceType] = 'flash-act';
+
+    static propertyDescriptors = {
+        // Update & expose
+
+        name: {
+            flags: {update: true, expose: true},
+
+            update: {
+                default: 'Unnamed Flash Act',
+                validate: isName
+            }
+        },
+
+        color: {
+            flags: {update: true, expose: true},
+            update: {validate: isColor}
+        },
+
+        anchor: {
+            flags: {update: true, expose: true},
+            update: {validate: isString}
+        },
+
+        jump: {
+            flags: {update: true, expose: true},
+            update: {validate: isString}
+        },
+
+        jumpColor: {
+            flags: {update: true, expose: true},
+            update: {validate: isColor}
+        },
+
+        flashesByRef: {
+            flags: {update: true, expose: true},
+            update: {validate: validateReferenceList('flash')}
+        },
+    };
+}
diff --git a/src/thing/validators.js b/src/thing/validators.js
index e745771a..a465e9d1 100644
--- a/src/thing/validators.js
+++ b/src/thing/validators.js
@@ -222,6 +222,18 @@ export function isDuration(duration) {
     return true;
 }
 
+export function isFileExtension(string) {
+    isStringNonEmpty(string);
+
+    if (string[0] === '.')
+        throw new TypeError(`Expected no dot (.) at the start of file extension`);
+
+    if (string.match(/[^a-zA-Z0-9_]/))
+        throw new TypeError(`Expected only alphanumeric and underscore`);
+
+    return true;
+}
+
 export function isName(name) {
     return isString(name);
 }
@@ -260,3 +272,36 @@ export function validateReference(type = 'track') {
 export function validateReferenceList(type = '') {
     return validateArrayItems(validateReference(type));
 }
+
+// Compositional utilities
+
+export function oneOf(...checks) {
+    return value => {
+        const errorMeta = [];
+
+        for (let i = 0, check; check = checks[i]; i++) {
+            try {
+                const result = check(value);
+
+                if (result !== true) {
+                    throw new Error(`Check returned false`);
+                }
+
+                return true;
+            } catch (error) {
+                errorMeta.push([check, i, error]);
+            }
+        }
+
+        // Don't process error messages until every check has failed.
+        const errors = [];
+        for (const [ check, i, error ] of errorMeta) {
+            error.message = (check.name
+                ? `(#${i} "${check.name}") ${error.message}`
+                : `(#${i}) ${error.message}`);
+            error.check = check;
+            errors.push(error);
+        }
+        throw new AggregateError(errors, `Expected one of ${checks.length} possible checks, but none were true`);
+    };
+}