From e17bf53b3f17d8d8aa62869d4fc97883acc5c1fa Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 1 Feb 2022 16:23:16 -0400 Subject: homepage layout data --- src/thing/homepage-layout.js | 99 ++++++++++++++++++++++++++++++ src/thing/validators.js | 7 +++ src/upd8.js | 143 ++++++++++++++++++++----------------------- test/data-validators.js | 11 ++++ 4 files changed, 183 insertions(+), 77 deletions(-) create mode 100644 src/thing/homepage-layout.js diff --git a/src/thing/homepage-layout.js b/src/thing/homepage-layout.js new file mode 100644 index 00000000..47173917 --- /dev/null +++ b/src/thing/homepage-layout.js @@ -0,0 +1,99 @@ +import CacheableObject from './cacheable-object.js'; + +import { + isColor, + isCountingNumber, + isName, + isString, + oneOf, + validateArrayItems, + validateInstanceOf, + validateReference, + validateReferenceList, +} from './validators.js'; + +export class HomepageLayoutRow extends CacheableObject { + static propertyDescriptors = { + // Update & expose + + name: { + flags: {update: true, expose: true}, + update: {validate: isName} + }, + + type: { + flags: {update: true, expose: true}, + + update: { + validate(value) { + throw new Error(`'type' property validator must be overridden`); + } + } + }, + + color: { + flags: {update: true, expose: true}, + update: {validate: isColor} + }, + }; +} + +export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { + static propertyDescriptors = { + ...HomepageLayoutRow.propertyDescriptors, + + // Update & expose + + type: { + flags: {update: true, expose: true}, + update: { + validate(value) { + if (value !== 'albums') { + throw new TypeError(`Expected 'albums'`); + } + + return true; + } + } + }, + + sourceGroupByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReference('group')} + }, + + sourceAlbumsByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReferenceList('album')} + }, + + countAlbumsFromGroup: { + flags: {update: true, expose: true}, + update: {validate: isCountingNumber} + }, + + actionLinks: { + flags: {update: true, expose: true}, + update: {validate: validateArrayItems(isString)} + }, + } +} + +export default class HomepageLayout extends CacheableObject { + static propertyDescriptors = { + // Update & expose + + sidebarContent: { + flags: {update: true, expose: true}, + update: {validate: isString} + }, + + rows: { + flags: {update: true, expose: true}, + + update: { + validate: validateArrayItems(validateInstanceOf(HomepageLayoutRow)) + } + }, + }; +} diff --git a/src/thing/validators.js b/src/thing/validators.js index a465e9d1..49463473 100644 --- a/src/thing/validators.js +++ b/src/thing/validators.js @@ -74,6 +74,13 @@ export function isInteger(number) { return true; } +export function isCountingNumber(number) { + isInteger(number); + isPositive(number); + + return true; +} + export function isString(value) { return isType(value, 'string'); } diff --git a/src/upd8.js b/src/upd8.js index 9c86c263..0f60802a 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -95,6 +95,9 @@ import Album, { TrackGroup } from './thing/album.js'; import Artist from './thing/artist.js'; import Flash, { FlashAct } from './thing/flash.js'; import Group, { GroupCategory } from './thing/group.js'; +import HomepageLayout, { + HomepageLayoutAlbumsRow, +} from './thing/homepage-layout.js'; import Thing from './thing/thing.js'; import Track from './thing/track.js'; @@ -198,7 +201,7 @@ const CACHEBUST = 7; // MAKE THESE END IN YAML const WIKI_INFO_FILE = 'wiki-info.txt'; -const HOMEPAGE_INFO_FILE = 'homepage.txt'; +const HOMEPAGE_LAYOUT_FILE = 'homepage.yaml'; const ARTIST_DATA_FILE = 'artists.yaml'; const FLASH_DATA_FILE = 'flashes.yaml'; const NEWS_DATA_FILE = 'news.txt'; @@ -750,6 +753,10 @@ function makeProcessDocument(thingClass, { // yet implemented as part of a Thing's data model! ignoredFields = [] }) { + if (!propertyFieldMapping) { + throw new Error(`Expected propertyFieldMapping to be provided`); + } + const knownFields = Object.values(propertyFieldMapping); // Invert the property-field mapping, since it'll come in handy for @@ -1367,72 +1374,50 @@ async function processWikiInfoFile(file) { }; } -async function processHomepageInfoFile(file) { - let contents; - try { - contents = await readFile(file, 'utf-8'); - } catch (error) { - return {error: `Could not read ${file} (${error.code}).`}; - } - - const contentLines = splitLines(contents); - const sections = Array.from(getSections(contentLines)); - - const [ firstSection, ...rowSections ] = sections; - - const sidebar = getMultilineField(firstSection, 'Sidebar'); +const processHomepageLayoutDocument = makeProcessDocument(HomepageLayout, { + propertyFieldMapping: { + sidebarContent: 'Sidebar Content' + }, - const validRowTypes = ['albums']; + ignoredFields: ['Homepage'] +}); - const rows = rowSections.map(section => { - const name = getBasicField(section, 'Row'); - if (!name) { - return {error: 'Expected "Row" (name) field!'}; - } +const homepageLayoutRowBaseSpec = { +}; - const color = getBasicField(section, 'Color'); +const makeProcessHomepageLayoutRowDocument = (rowClass, spec) => makeProcessDocument(rowClass, { + ...spec, - const type = getBasicField(section, 'Type'); - if (!type) { - return {error: 'Expected "Type" field!'}; - } + propertyFieldMapping: { + name: 'Row', + color: 'Color', + type: 'Type', + ...spec.propertyFieldMapping, + } +}); - if (!validRowTypes.includes(type)) { - return {error: `Expected "Type" field to be one of: ${validRowTypes.join(', ')}`}; +const homepageLayoutRowTypeProcessMapping = { + albums: makeProcessHomepageLayoutRowDocument(HomepageLayoutAlbumsRow, { + propertyFieldMapping: { + sourceGroupByRef: 'Group', + countAlbumsFromGroup: 'Count', + sourceAlbumsByRef: 'Albums', + actionLinks: 'Actions' } + }) +}; - const row = {name, color, type}; - - switch (type) { - case 'albums': { - const group = getBasicField(section, 'Group') || null; - const albums = getListField(section, 'Albums') || []; - - if (!group && !albums) { - return {error: 'Expected "Group" and/or "Albums" field!'}; - } - - let groupCount = getBasicField(section, 'Count'); - if (group && !groupCount) { - return {error: 'Expected "Count" field!'}; - } - - if (groupCount) { - if (isNaN(parseInt(groupCount))) { - return {error: `Invalid Count field: "${groupCount}"`}; - } - - groupCount = parseInt(groupCount); - } +function processHomepageLayoutRowDocument(document) { + const type = document['Type']; - const actions = getListField(section, 'Actions') || []; + const match = Object.entries(homepageLayoutRowTypeProcessMapping) + .find(([ key ]) => key === type); - return {...row, group, groupCount, albums, actions}; - } - } - }); + if (!match) { + throw new TypeError(`No processDocument function for row type ${type}!`); + } - return {sidebar, rows}; + return match[1](document); } function getDurationInSeconds(string) { @@ -2400,23 +2385,6 @@ async function main() { } else { languages.default = defaultStrings; } - - WD.homepageInfo = await processHomepageInfoFile(path.join(dataPath, HOMEPAGE_INFO_FILE)); - - if (WD.homepageInfo.error) { - console.log(`\x1b[31;1m${WD.homepageInfo.error}\x1b[0m`); - return; - } - - { - const errors = WD.homepageInfo.rows.filter(obj => obj.error); - if (errors.length) { - for (const error of errors) { - console.log(`\x1b[31;1m${error.error}\x1b[0m`); - } - return; - } - } */ // 8ut wait, you might say, how do we know which al8um these data files @@ -2528,7 +2496,7 @@ async function main() { }, { - title: `Process artist file`, + title: `Process artists file`, files: [path.join(dataPath, ARTIST_DATA_FILE)], documentMode: documentModes.allInOne, @@ -2541,7 +2509,7 @@ async function main() { // TODO: WD.wikiInfo.features.flashesAndGames && { - title: `Process flash file`, + title: `Process flashes file`, files: [path.join(dataPath, FLASH_DATA_FILE)], documentMode: documentModes.allInOne, @@ -2582,7 +2550,7 @@ async function main() { }, { - title: `Process group file`, + title: `Process groups file`, files: [path.join(dataPath, GROUP_DATA_FILE)], documentMode: documentModes.allInOne, @@ -2621,6 +2589,25 @@ async function main() { wikiData.groupCategoryData = results.filter(x => x instanceof GroupCategory); } }, + + { + title: `Process homepage layout file`, + files: [path.join(dataPath, HOMEPAGE_LAYOUT_FILE)], + + documentMode: documentModes.headerAndEntries, + processHeaderDocument: processHomepageLayoutDocument, + processEntryDocument: processHomepageLayoutRowDocument, + + save(results) { + if (!results[0]) { + return; + } + + const { header: homepageLayout, entries: rows } = results[0]; + Object.assign(homepageLayout, {rows}); + Object.assign(wikiData, {homepageLayout}); + } + }, ]; const processDataAggregate = openAggregate({message: `Errors processing data files`}); @@ -2765,6 +2752,8 @@ async function main() { if (wikiData.flashData) logInfo` - ${wikiData.flashData.length} flashes (${wikiData.flashActData.length} acts)`; logInfo` - ${wikiData.groupData.length} groups (${wikiData.groupCategoryData.length} categories)`; + if (wikiData.homepageLayout) + logInfo` - ${1} homepage layout (${wikiData.homepageLayout.rows.length} rows)`; } catch (error) { console.error(`Error showing data summary:`, error); } diff --git a/test/data-validators.js b/test/data-validators.js index feec127a..739333a3 100644 --- a/test/data-validators.js +++ b/test/data-validators.js @@ -4,6 +4,7 @@ import { showAggregate } from '../src/util/sugar.js'; import { // Basic types isBoolean, + isCountingNumber, isNumber, isString, isStringNonEmpty, @@ -60,6 +61,16 @@ test('isNumber', t => { t.throws(() => isNumber(true), TypeError); }); +test('isCountingNumber', t => { + t.plan(6); + t.ok(isCountingNumber(3)); + t.ok(isCountingNumber(1)); + t.throws(() => isCountingNumber(1.75), TypeError); + t.throws(() => isCountingNumber(0), TypeError); + t.throws(() => isCountingNumber(-1), TypeError); + t.throws(() => isCountingNumber('612'), TypeError); +}); + test('isString', t => { t.plan(3); t.ok(isString('hello!')); -- cgit 1.3.0-6-gf8a5