From aa3cb2dd34780fd97873340c3faf7388993fa8d8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 26 Jan 2026 13:38:13 -0400 Subject: data: split group.js --- src/data/things/group.js | 358 --------------------------------- src/data/things/group/Group.js | 232 +++++++++++++++++++++ src/data/things/group/GroupCategory.js | 58 ++++++ src/data/things/group/Series.js | 79 ++++++++ src/data/things/group/index.js | 3 + src/data/things/index.js | 2 +- 6 files changed, 373 insertions(+), 359 deletions(-) delete mode 100644 src/data/things/group.js create mode 100644 src/data/things/group/Group.js create mode 100644 src/data/things/group/GroupCategory.js create mode 100644 src/data/things/group/Series.js create mode 100644 src/data/things/group/index.js diff --git a/src/data/things/group.js b/src/data/things/group.js deleted file mode 100644 index cc5f4cb8..00000000 --- a/src/data/things/group.js +++ /dev/null @@ -1,358 +0,0 @@ -export const GROUP_DATA_FILE = 'groups.yaml'; - -import {inspect} from 'node:util'; - -import {colors} from '#cli'; -import {input, V} from '#composite'; -import Thing from '#thing'; -import {is, isBoolean} from '#validators'; -import {parseAnnotatedReferences, parseSerieses} from '#yaml'; - -import {withPropertyFromObject} from '#composite/data'; -import {withUniqueReferencingThing} from '#composite/wiki-data'; - -import { - exposeConstant, - exposeDependencyOrContinue, - exposeUpdateValueOrContinue, -} from '#composite/control-flow'; - -import { - annotatedReferenceList, - color, - contentString, - directory, - flag, - name, - referenceList, - soupyFind, - soupyReverse, - thing, - thingList, - urls, -} from '#composite/wiki-properties'; - -export class Group extends Thing { - static [Thing.referenceType] = 'group'; - static [Thing.wikiData] = 'groupData'; - - static [Thing.getPropertyDescriptors] = ({Album, Artist, Series}) => ({ - // Update & expose - - name: name(V('Unnamed Group')), - directory: directory(), - - excludeFromGalleryTabs: [ - exposeUpdateValueOrContinue({ - validate: input.value(isBoolean), - }), - - withUniqueReferencingThing({ - reverse: soupyReverse.input('groupCategoriesWhichInclude'), - }).outputs({ - '#uniqueReferencingThing': '#category', - }), - - withPropertyFromObject('#category', V('excludeGroupsFromGalleryTabs')), - exposeDependencyOrContinue('#category.excludeGroupsFromGalleryTabs'), - - exposeConstant(V(false)), - ], - - divideAlbumsByStyle: flag(V(false)), - - description: contentString(), - - urls: urls(), - - closelyLinkedArtists: annotatedReferenceList({ - class: input.value(Artist), - find: soupyFind.input('artist'), - - reference: input.value('artist'), - thing: input.value('artist'), - }), - - featuredAlbums: referenceList({ - class: input.value(Album), - find: soupyFind.input('album'), - }), - - serieses: thingList(V(Series)), - - // Update only - - find: soupyFind(), - reverse: soupyReverse(), - - // Expose only - - isGroup: exposeConstant(V(true)), - - descriptionShort: { - flags: {expose: true}, - - expose: { - dependencies: ['description'], - compute: ({description}) => - (description - ? description.split('
')[0] - : null), - }, - }, - - albums: { - flags: {expose: true}, - - expose: { - dependencies: ['this', '_reverse'], - compute: ({this: group, _reverse: reverse}) => - reverse.albumsWhoseGroupsInclude(group), - }, - }, - - color: { - flags: {expose: true}, - - expose: { - dependencies: ['this', '_reverse'], - compute: ({this: group, _reverse: reverse}) => - reverse.groupCategoriesWhichInclude(group, {unique: true}) - ?.color, - }, - }, - - category: { - flags: {expose: true}, - - expose: { - dependencies: ['this', '_reverse'], - compute: ({this: group, _reverse: reverse}) => - reverse.groupCategoriesWhichInclude(group, {unique: true}) ?? - null, - }, - }, - }); - - static [Thing.findSpecs] = { - group: { - referenceTypes: ['group', 'group-gallery'], - bindTo: 'groupData', - }, - }; - - static [Thing.reverseSpecs] = { - groupsCloselyLinkedTo: { - bindTo: 'groupData', - - referencing: group => - group.closelyLinkedArtists - .map(({artist, ...referenceDetails}) => ({ - group, - artist, - referenceDetails, - })), - - referenced: ({artist}) => [artist], - - tidy: ({group, referenceDetails}) => - ({group, ...referenceDetails}), - }, - }; - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Group': {property: 'name'}, - 'Directory': {property: 'directory'}, - - 'Exclude From Gallery Tabs': {property: 'excludeFromGalleryTabs'}, - 'Divide Albums By Style': {property: 'divideAlbumsByStyle'}, - - 'Description': {property: 'description'}, - 'URLs': {property: 'urls'}, - - 'Closely Linked Artists': { - property: 'closelyLinkedArtists', - transform: value => - parseAnnotatedReferences(value, { - referenceField: 'Artist', - referenceProperty: 'artist', - }), - }, - - 'Featured Albums': {property: 'featuredAlbums'}, - - 'Series': { - property: 'serieses', - transform: parseSerieses, - }, - - 'Review Points': {ignore: true}, - }, - }; - - static [Thing.getYamlLoadingSpec] = ({ - documentModes: {allInOne}, - thingConstructors: {Group, GroupCategory}, - }) => ({ - title: `Process groups file`, - file: GROUP_DATA_FILE, - - documentMode: allInOne, - documentThing: document => - ('Category' in document - ? GroupCategory - : Group), - - connect(results) { - let groupCategory; - let groupRefs = []; - - if (results[0] && !(results[0] instanceof GroupCategory)) { - throw new Error(`Expected a category at top of group data file`); - } - - for (const thing of results) { - if (thing instanceof GroupCategory) { - if (groupCategory) { - Object.assign(groupCategory, {groups: groupRefs}); - } - - groupCategory = thing; - groupRefs = []; - } else { - groupRefs.push(Thing.getReference(thing)); - } - } - - if (groupCategory) { - Object.assign(groupCategory, {groups: groupRefs}); - } - }, - - // Groups aren't sorted at all, always preserving the order in the data - // file as-is. - sort: null, - }); -} - -export class GroupCategory extends Thing { - static [Thing.referenceType] = 'group-category'; - static [Thing.friendlyName] = `Group Category`; - static [Thing.wikiData] = 'groupCategoryData'; - - static [Thing.getPropertyDescriptors] = ({Group}) => ({ - // Update & expose - - name: name(V('Unnamed Group Category')), - directory: directory(), - - excludeGroupsFromGalleryTabs: flag(V(false)), - - color: color(), - - groups: referenceList({ - class: input.value(Group), - find: soupyFind.input('group'), - }), - - // Update only - - find: soupyFind(), - - // Expose only - - isGroupCategory: exposeConstant(V(true)), - }); - - static [Thing.reverseSpecs] = { - groupCategoriesWhichInclude: { - bindTo: 'groupCategoryData', - - referencing: groupCategory => [groupCategory], - referenced: groupCategory => groupCategory.groups, - }, - }; - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Category': {property: 'name'}, - - 'Color': {property: 'color'}, - - 'Exclude Groups From Gallery Tabs': { - property: 'excludeGroupsFromGalleryTabs', - }, - }, - }; -} - -export class Series extends Thing { - static [Thing.wikiData] = 'seriesData'; - - static [Thing.getPropertyDescriptors] = ({Album, Group}) => ({ - // Update & expose - - name: name(V('Unnamed Series')), - - showAlbumArtists: { - flags: {update: true, expose: true}, - update: { - validate: - is('all', 'differing', 'none'), - }, - }, - - description: contentString(), - - group: thing(V(Group)), - - albums: referenceList({ - class: input.value(Album), - find: soupyFind.input('album'), - }), - - // Update only - - find: soupyFind(), - }); - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Name': {property: 'name'}, - - 'Description': {property: 'description'}, - - 'Show Album Artists': {property: 'showAlbumArtists'}, - - 'Albums': {property: 'albums'}, - }, - }; - - [inspect.custom](depth, options, inspect) { - const parts = []; - - parts.push(Thing.prototype[inspect.custom].apply(this)); - - if (depth >= 0) showGroup: { - let group = null; - try { - group = this.group; - } catch { - break showGroup; - } - - const groupName = group.name; - const groupIndex = group.serieses.indexOf(this); - - const num = - (groupIndex === -1 - ? 'indeterminate position' - : `#${groupIndex + 1}`); - - parts.push(` (${colors.yellow(num)} in ${colors.green(`"${groupName}"`)})`); - } - - return parts.join(''); - } -} diff --git a/src/data/things/group/Group.js b/src/data/things/group/Group.js new file mode 100644 index 00000000..b065f9a3 --- /dev/null +++ b/src/data/things/group/Group.js @@ -0,0 +1,232 @@ +const GROUP_DATA_FILE = 'groups.yaml'; + +import {input, V} from '#composite'; +import Thing from '#thing'; +import {isBoolean} from '#validators'; +import {parseAnnotatedReferences, parseSerieses} from '#yaml'; + +import {withPropertyFromObject} from '#composite/data'; +import {withUniqueReferencingThing} from '#composite/wiki-data'; + +import { + exposeConstant, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import { + annotatedReferenceList, + contentString, + directory, + flag, + name, + referenceList, + soupyFind, + soupyReverse, + thingList, + urls, +} from '#composite/wiki-properties'; + +export class Group extends Thing { + static [Thing.referenceType] = 'group'; + static [Thing.wikiData] = 'groupData'; + + static [Thing.getPropertyDescriptors] = ({Album, Artist, Series}) => ({ + // Update & expose + + name: name(V('Unnamed Group')), + directory: directory(), + + excludeFromGalleryTabs: [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withUniqueReferencingThing({ + reverse: soupyReverse.input('groupCategoriesWhichInclude'), + }).outputs({ + '#uniqueReferencingThing': '#category', + }), + + withPropertyFromObject('#category', V('excludeGroupsFromGalleryTabs')), + exposeDependencyOrContinue('#category.excludeGroupsFromGalleryTabs'), + + exposeConstant(V(false)), + ], + + divideAlbumsByStyle: flag(V(false)), + + description: contentString(), + + urls: urls(), + + closelyLinkedArtists: annotatedReferenceList({ + class: input.value(Artist), + find: soupyFind.input('artist'), + + reference: input.value('artist'), + thing: input.value('artist'), + }), + + featuredAlbums: referenceList({ + class: input.value(Album), + find: soupyFind.input('album'), + }), + + serieses: thingList(V(Series)), + + // Update only + + find: soupyFind(), + reverse: soupyReverse(), + + // Expose only + + isGroup: exposeConstant(V(true)), + + descriptionShort: { + flags: {expose: true}, + + expose: { + dependencies: ['description'], + compute: ({description}) => + (description + ? description.split('
')[0] + : null), + }, + }, + + albums: { + flags: {expose: true}, + + expose: { + dependencies: ['this', '_reverse'], + compute: ({this: group, _reverse: reverse}) => + reverse.albumsWhoseGroupsInclude(group), + }, + }, + + color: { + flags: {expose: true}, + + expose: { + dependencies: ['this', '_reverse'], + compute: ({this: group, _reverse: reverse}) => + reverse.groupCategoriesWhichInclude(group, {unique: true}) + ?.color, + }, + }, + + category: { + flags: {expose: true}, + + expose: { + dependencies: ['this', '_reverse'], + compute: ({this: group, _reverse: reverse}) => + reverse.groupCategoriesWhichInclude(group, {unique: true}) ?? + null, + }, + }, + }); + + static [Thing.findSpecs] = { + group: { + referenceTypes: ['group', 'group-gallery'], + bindTo: 'groupData', + }, + }; + + static [Thing.reverseSpecs] = { + groupsCloselyLinkedTo: { + bindTo: 'groupData', + + referencing: group => + group.closelyLinkedArtists + .map(({artist, ...referenceDetails}) => ({ + group, + artist, + referenceDetails, + })), + + referenced: ({artist}) => [artist], + + tidy: ({group, referenceDetails}) => + ({group, ...referenceDetails}), + }, + }; + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Group': {property: 'name'}, + 'Directory': {property: 'directory'}, + + 'Exclude From Gallery Tabs': {property: 'excludeFromGalleryTabs'}, + 'Divide Albums By Style': {property: 'divideAlbumsByStyle'}, + + 'Description': {property: 'description'}, + 'URLs': {property: 'urls'}, + + 'Closely Linked Artists': { + property: 'closelyLinkedArtists', + transform: value => + parseAnnotatedReferences(value, { + referenceField: 'Artist', + referenceProperty: 'artist', + }), + }, + + 'Featured Albums': {property: 'featuredAlbums'}, + + 'Series': { + property: 'serieses', + transform: parseSerieses, + }, + + 'Review Points': {ignore: true}, + }, + }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {allInOne}, + thingConstructors: {Group, GroupCategory}, + }) => ({ + title: `Process groups file`, + file: GROUP_DATA_FILE, + + documentMode: allInOne, + documentThing: document => + ('Category' in document + ? GroupCategory + : Group), + + connect(results) { + let groupCategory; + let groupRefs = []; + + if (results[0] && !(results[0] instanceof GroupCategory)) { + throw new Error(`Expected a category at top of group data file`); + } + + for (const thing of results) { + if (thing instanceof GroupCategory) { + if (groupCategory) { + Object.assign(groupCategory, {groups: groupRefs}); + } + + groupCategory = thing; + groupRefs = []; + } else { + groupRefs.push(Thing.getReference(thing)); + } + } + + if (groupCategory) { + Object.assign(groupCategory, {groups: groupRefs}); + } + }, + + // Groups aren't sorted at all, always preserving the order in the data + // file as-is. + sort: null, + }); +} diff --git a/src/data/things/group/GroupCategory.js b/src/data/things/group/GroupCategory.js new file mode 100644 index 00000000..daf31868 --- /dev/null +++ b/src/data/things/group/GroupCategory.js @@ -0,0 +1,58 @@ +import {input, V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {color, directory, flag, name, referenceList, soupyFind} + from '#composite/wiki-properties'; + +export class GroupCategory extends Thing { + static [Thing.referenceType] = 'group-category'; + static [Thing.friendlyName] = `Group Category`; + static [Thing.wikiData] = 'groupCategoryData'; + + static [Thing.getPropertyDescriptors] = ({Group}) => ({ + // Update & expose + + name: name(V('Unnamed Group Category')), + directory: directory(), + + excludeGroupsFromGalleryTabs: flag(V(false)), + + color: color(), + + groups: referenceList({ + class: input.value(Group), + find: soupyFind.input('group'), + }), + + // Update only + + find: soupyFind(), + + // Expose only + + isGroupCategory: exposeConstant(V(true)), + }); + + static [Thing.reverseSpecs] = { + groupCategoriesWhichInclude: { + bindTo: 'groupCategoryData', + + referencing: groupCategory => [groupCategory], + referenced: groupCategory => groupCategory.groups, + }, + }; + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Category': {property: 'name'}, + + 'Color': {property: 'color'}, + + 'Exclude Groups From Gallery Tabs': { + property: 'excludeGroupsFromGalleryTabs', + }, + }, + }; +} diff --git a/src/data/things/group/Series.js b/src/data/things/group/Series.js new file mode 100644 index 00000000..940fe575 --- /dev/null +++ b/src/data/things/group/Series.js @@ -0,0 +1,79 @@ +import {inspect} from 'node:util'; + +import {colors} from '#cli'; +import {input, V} from '#composite'; +import Thing from '#thing'; +import {is} from '#validators'; + +import {contentString, name, referenceList, soupyFind, thing} + from '#composite/wiki-properties'; + +export class Series extends Thing { + static [Thing.wikiData] = 'seriesData'; + + static [Thing.getPropertyDescriptors] = ({Album, Group}) => ({ + // Update & expose + + name: name(V('Unnamed Series')), + + showAlbumArtists: { + flags: {update: true, expose: true}, + update: { + validate: + is('all', 'differing', 'none'), + }, + }, + + description: contentString(), + + group: thing(V(Group)), + + albums: referenceList({ + class: input.value(Album), + find: soupyFind.input('album'), + }), + + // Update only + + find: soupyFind(), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Name': {property: 'name'}, + + 'Description': {property: 'description'}, + + 'Show Album Artists': {property: 'showAlbumArtists'}, + + 'Albums': {property: 'albums'}, + }, + }; + + [inspect.custom](depth, options, inspect) { + const parts = []; + + parts.push(Thing.prototype[inspect.custom].apply(this)); + + if (depth >= 0) showGroup: { + let group = null; + try { + group = this.group; + } catch { + break showGroup; + } + + const groupName = group.name; + const groupIndex = group.serieses.indexOf(this); + + const num = + (groupIndex === -1 + ? 'indeterminate position' + : `#${groupIndex + 1}`); + + parts.push(` (${colors.yellow(num)} in ${colors.green(`"${groupName}"`)})`); + } + + return parts.join(''); + } +} diff --git a/src/data/things/group/index.js b/src/data/things/group/index.js new file mode 100644 index 00000000..1723f136 --- /dev/null +++ b/src/data/things/group/index.js @@ -0,0 +1,3 @@ +export * from './Group.js'; +export * from './GroupCategory.js'; +export * from './Series.js'; diff --git a/src/data/things/index.js b/src/data/things/index.js index 74c6f1e4..4a3d7ad8 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -1,6 +1,7 @@ // Not actually the entry point for #things - that's init.js in this folder. export * from './album/index.js'; +export * from './group/index.js'; export * from './homepage-layout/index.js'; export * from './AdditionalFile.js'; @@ -18,5 +19,4 @@ export * from './WikiInfo.js'; export * from './content.js'; export * from './flash.js'; -export * from './group.js'; export * from './sorting-rule.js'; -- cgit 1.3.0-6-gf8a5