diff options
Diffstat (limited to 'src/thing')
-rw-r--r-- | src/thing/album.js | 5 | ||||
-rw-r--r-- | src/thing/cacheable-object.js | 6 | ||||
-rw-r--r-- | src/thing/flash.js | 129 | ||||
-rw-r--r-- | src/thing/validators.js | 45 |
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`); + }; +} |