diff options
Diffstat (limited to 'src/data/things/homepage-layout.js')
-rw-r--r-- | src/data/things/homepage-layout.js | 301 |
1 files changed, 214 insertions, 87 deletions
diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js index 47d92471..3a11c287 100644 --- a/src/data/things/homepage-layout.js +++ b/src/data/things/homepage-layout.js @@ -1,7 +1,11 @@ export const HOMEPAGE_LAYOUT_DATA_FILE = 'homepage.yaml'; +import {inspect} from 'node:util'; + +import {colors} from '#cli'; import {input} from '#composite'; import Thing from '#thing'; +import {empty} from '#sugar'; import { anyOf, @@ -10,19 +14,26 @@ import { isString, isStringNonEmpty, validateArrayItems, - validateInstanceOf, validateReference, } from '#validators'; import {exposeDependency} from '#composite/control-flow'; import {withResolvedReference} from '#composite/wiki-data'; -import {color, contentString, name, referenceList, soupyFind} - from '#composite/wiki-properties'; + +import { + color, + contentString, + name, + referenceList, + soupyFind, + thing, + thingList, +} from '#composite/wiki-properties'; export class HomepageLayout extends Thing { static [Thing.friendlyName] = `Homepage Layout`; - static [Thing.getPropertyDescriptors] = ({HomepageLayoutRow}) => ({ + static [Thing.getPropertyDescriptors] = ({HomepageLayoutSection}) => ({ // Update & expose sidebarContent: contentString(), @@ -30,15 +41,12 @@ export class HomepageLayout extends Thing { navbarLinks: { flags: {update: true, expose: true}, update: {validate: validateArrayItems(isStringNonEmpty)}, + expose: {transform: value => value ?? []}, }, - rows: { - flags: {update: true, expose: true}, - - update: { - validate: validateArrayItems(validateInstanceOf(HomepageLayoutRow)), - }, - }, + sections: thingList({ + class: input.value(HomepageLayoutSection), + }), }); static [Thing.yamlDocumentSpec] = { @@ -49,75 +57,230 @@ export class HomepageLayout extends Thing { 'Navbar Links': {property: 'navbarLinks'}, }, }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {allInOne}, + thingConstructors: { + HomepageLayout, + HomepageLayoutSection, + }, + }) => ({ + title: `Process homepage layout file`, + file: HOMEPAGE_LAYOUT_DATA_FILE, + + documentMode: allInOne, + documentThing: document => { + if (document['Homepage']) { + return HomepageLayout; + } + + if (document['Section']) { + return HomepageLayoutSection; + } + + if (document['Row']) { + switch (document['Row']) { + case 'actions': + return HomepageLayoutActionsRow; + case 'album carousel': + return HomepageLayoutAlbumCarouselRow; + case 'album grid': + return HomepageLayoutAlbumGridRow; + default: + throw new TypeError(`Unrecognized row type ${document['Row']}`); + } + } + + return null; + }, + + save(results) { + if (!empty(results) && !(results[0] instanceof HomepageLayout)) { + throw new Error(`Expected 'Homepage' document at top of homepage layout file`); + } + + const homepageLayout = results[0]; + const sections = []; + + let currentSection = null; + let currentSectionRows = []; + + const closeCurrentSection = () => { + if (currentSection) { + for (const row of currentSectionRows) { + row.section = currentSection; + } + + currentSection.rows = currentSectionRows; + sections.push(currentSection); + + currentSection = null; + currentSectionRows = []; + } + }; + + for (const entry of results.slice(1)) { + if (entry instanceof HomepageLayout) { + throw new Error(`Expected only one 'Homepage' document in total`); + } else if (entry instanceof HomepageLayoutSection) { + closeCurrentSection(); + currentSection = entry; + } else if (entry instanceof HomepageLayoutRow) { + if (currentSection) { + currentSectionRows.push(entry); + } else { + throw new Error(`Expected a 'Section' document to add following rows into`); + } + } + } + + closeCurrentSection(); + + homepageLayout.sections = sections; + + return {homepageLayout}; + }, + }); +} + +export class HomepageLayoutSection extends Thing { + static [Thing.friendlyName] = `Homepage Section`; + + static [Thing.getPropertyDescriptors] = ({HomepageLayoutRow}) => ({ + // Update & expose + + name: name(`Unnamed Homepage Section`), + + color: color(), + + rows: thingList({ + class: input.value(HomepageLayoutRow), + }), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Section': {property: 'name'}, + 'Color': {property: 'color'}, + }, + }; } export class HomepageLayoutRow extends Thing { static [Thing.friendlyName] = `Homepage Row`; - static [Thing.getPropertyDescriptors] = () => ({ + static [Thing.getPropertyDescriptors] = ({HomepageLayoutSection}) => ({ // Update & expose - name: name('Unnamed Homepage Row'), + section: thing({ + class: input.value(HomepageLayoutSection), + }), + + // Update only + + find: soupyFind(), + + // Expose only type: { - flags: {update: true, expose: true}, + flags: {expose: true}, - update: { - validate() { + expose: { + compute() { throw new Error(`'type' property validator must be overridden`); }, }, }, - - color: color(), - - // Update only - - find: soupyFind(), }); static [Thing.yamlDocumentSpec] = { fields: { - 'Row': {property: 'name'}, - 'Color': {property: 'color'}, - 'Type': {property: 'type'}, + 'Row': {ignore: true}, }, }; + + [inspect.custom](depth) { + const parts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (depth >= 0 && this.section) { + const sectionName = this.section.name; + const index = this.section.rows.indexOf(this); + const rowNum = + (index === -1 + ? 'indeterminate position' + : `#${index + 1}`); + parts.push(` (${colors.yellow(rowNum)} in ${colors.green(sectionName)})`); + } + + return parts.join(''); + } } -export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { - static [Thing.friendlyName] = `Homepage Albums Row`; +export class HomepageLayoutActionsRow extends HomepageLayoutRow { + static [Thing.friendlyName] = `Homepage Actions Row`; - static [Thing.getPropertyDescriptors] = (opts, {Album, Group} = opts) => ({ + static [Thing.getPropertyDescriptors] = (opts) => ({ ...HomepageLayoutRow[Thing.getPropertyDescriptors](opts), // Update & expose - type: { + actionLinks: { flags: {update: true, expose: true}, - update: { - validate(value) { - if (value !== 'albums') { - throw new TypeError(`Expected 'albums'`); - } + update: {validate: validateArrayItems(isString)}, + }, - return true; - }, - }, + // Expose only + + type: { + flags: {expose: true}, + expose: {compute: () => 'actions'}, }, + }); - displayStyle: { - flags: {update: true, expose: true}, + static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(HomepageLayoutRow, { + fields: { + 'Actions': {property: 'actionLinks'}, + }, + }); +} - update: { - validate: is('grid', 'carousel'), - }, +export class HomepageLayoutAlbumCarouselRow extends HomepageLayoutRow { + static [Thing.friendlyName] = `Homepage Album Carousel Row`; - expose: { - transform: (displayStyle) => - displayStyle ?? 'grid', - }, + static [Thing.getPropertyDescriptors] = (opts, {Album} = opts) => ({ + ...HomepageLayoutRow[Thing.getPropertyDescriptors](opts), + + // Update & expose + + albums: referenceList({ + class: input.value(Album), + find: soupyFind.input('album'), + }), + + // Expose only + + type: { + flags: {expose: true}, + expose: {compute: () => 'album carousel'}, }, + }); + + static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(HomepageLayoutRow, { + fields: { + 'Albums': {property: 'albums'}, + }, + }); +} + +export class HomepageLayoutAlbumGridRow extends HomepageLayoutRow { + static [Thing.friendlyName] = `Homepage Album Grid Row`; + + static [Thing.getPropertyDescriptors] = (opts, {Album, Group} = opts) => ({ + ...HomepageLayoutRow[Thing.getPropertyDescriptors](opts), + + // Update & expose sourceGroup: [ { @@ -156,55 +319,19 @@ export class HomepageLayoutAlbumsRow extends HomepageLayoutRow { update: {validate: isCountingNumber}, }, - actionLinks: { - flags: {update: true, expose: true}, - update: {validate: validateArrayItems(isString)}, + // Expose only + + type: { + flags: {expose: true}, + expose: {compute: () => 'album grid'}, }, }); static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(HomepageLayoutRow, { fields: { - 'Display Style': {property: 'displayStyle'}, 'Group': {property: 'sourceGroup'}, 'Count': {property: 'countAlbumsFromGroup'}, 'Albums': {property: 'sourceAlbums'}, - 'Actions': {property: 'actionLinks'}, - }, - }); - - static [Thing.getYamlLoadingSpec] = ({ - documentModes: {headerAndEntries}, // Kludge, see below - thingConstructors: { - HomepageLayout, - HomepageLayoutAlbumsRow, - }, - }) => ({ - title: `Process homepage layout file`, - - // Kludge: This benefits from the same headerAndEntries style messaging as - // albums and tracks (for example), but that document mode is designed to - // support multiple files, and only one is actually getting processed here. - files: [HOMEPAGE_LAYOUT_DATA_FILE], - - documentMode: headerAndEntries, - headerDocumentThing: HomepageLayout, - entryDocumentThing: document => { - switch (document['Type']) { - case 'albums': - return HomepageLayoutAlbumsRow; - default: - throw new TypeError(`No processDocument function for row type ${document['Type']}!`); - } - }, - - save(results) { - if (!results[0]) { - return; - } - - const {header: homepageLayout, entries: rows} = results[0]; - Object.assign(homepageLayout, {rows}); - return {homepageLayout}; }, }); } |