From 62d7e0146d471e7623e4ec384a928a828b8780e9 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 5 Oct 2023 23:22:43 -0300 Subject: content, data: experimental art tag sidebar --- .../generateArtTagAncestorDescendantMapList.js | 129 +++++++++++++++++++++ .../generateArtTagAncestorSidebarBox.js | 27 +++++ src/content/dependencies/generateArtTagInfoPage.js | 9 +- src/content/dependencies/generateArtTagSidebar.js | 31 +++++ src/data/composite/things/art-tag/index.js | 1 + .../things/art-tag/withAncestorArtTagBaobabTree.js | 46 ++++++++ src/data/things/art-tag.js | 8 +- src/data/things/language.js | 1 + src/static/site4.css | 9 ++ src/strings-default.json | 8 ++ src/util/wiki-data.js | 32 +++++ 11 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 src/content/dependencies/generateArtTagAncestorDescendantMapList.js create mode 100644 src/content/dependencies/generateArtTagAncestorSidebarBox.js create mode 100644 src/content/dependencies/generateArtTagSidebar.js create mode 100644 src/data/composite/things/art-tag/withAncestorArtTagBaobabTree.js diff --git a/src/content/dependencies/generateArtTagAncestorDescendantMapList.js b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js new file mode 100644 index 00000000..6b4e52df --- /dev/null +++ b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js @@ -0,0 +1,129 @@ +import {stitchArrays} from '#sugar'; +import {filterMultipleArrays, sortMultipleArrays} from '#wiki-data'; + +export default { + contentDependencies: ['linkArtTagDynamically'], + extraDependencies: ['html', 'language'], + + // Recursion ain't too pretty! + + query(ancestorArtTag, targetArtTag) { + const recursive = artTag => { + const artTags = + artTag.directDescendantArtTags.slice(); + + const displayBriefly = + !artTags.includes(targetArtTag) && + artTags.length > 3; + + const artTagsIncludeTargetArtTag = + artTags.map(artTag => artTag.allDescendantArtTags.includes(targetArtTag)); + + const numExemptArtTags = + (displayBriefly + ? artTagsIncludeTargetArtTag + .filter(includesTargetArtTag => !includesTargetArtTag) + .length + : null); + + const sublists = + stitchArrays({ + artTag: artTags, + includesTargetArtTag: artTagsIncludeTargetArtTag, + }).map(({artTag, includesTargetArtTag}) => + (includesTargetArtTag + ? recursive(artTag) + : null)); + + if (displayBriefly) { + filterMultipleArrays(artTags, sublists, + (artTag, sublist) => + artTag === targetArtTag || + sublist !== null); + } else { + sortMultipleArrays(artTags, sublists, + (artTagA, artTagB, sublistA, sublistB) => + (sublistA && sublistB + ? 0 + : !sublistA && !sublistB + ? 0 + : sublistA + ? 1 + : -1)); + } + + return { + displayBriefly, + numExemptArtTags, + artTags, + sublists, + }; + }; + + return {root: recursive(ancestorArtTag)}; + }, + + relations(relation, query, _ancestorArtTag, _targetArtTag) { + const recursive = ({artTags, sublists}) => ({ + artTagLinks: + artTags + .map(artTag => relation('linkArtTagDynamically', artTag)), + + sublists: + sublists + .map(sublist => (sublist ? recursive(sublist) : null)), + }); + + return {root: recursive(query.root)}; + }, + + data(query, _ancestorArtTag, targetArtTag) { + const recursive = ({displayBriefly, numExemptArtTags, artTags, sublists}) => ({ + displayBriefly, + numExemptArtTags, + + artTagsAreTargetTag: + artTags + .map(artTag => artTag === targetArtTag), + + sublists: + sublists + .map(sublist => (sublist ? recursive(sublist) : null)), + }); + + return {root: recursive(query.root)}; + }, + + generate(data, relations, {html, language}) { + const recursive = (dataNode, relationsNode) => + html.tag('dl', [ + dataNode.displayBriefly && + html.tag('dt', + language.$('artTagSidebar.otherTagsExempt', { + tags: + language.countArtTags(dataNode.numExemptArtTags, {unit: true}), + })), + + stitchArrays({ + isTargetTag: dataNode.artTagsAreTargetTag, + dataSublist: dataNode.sublists, + + artTagLink: relationsNode.artTagLinks, + relationsSublist: relationsNode.sublists, + }).map(({ + isTargetTag, dataSublist, + artTagLink, relationsSublist, + }) => [ + html.tag('dt', + {class: (dataSublist || isTargetTag) && 'current'}, + artTagLink), + + dataSublist && + html.tag('dd', + recursive(dataSublist, relationsSublist)), + ]), + ]); + + return recursive(data.root, relations.root); + }, +}; diff --git a/src/content/dependencies/generateArtTagAncestorSidebarBox.js b/src/content/dependencies/generateArtTagAncestorSidebarBox.js new file mode 100644 index 00000000..ea85c2b2 --- /dev/null +++ b/src/content/dependencies/generateArtTagAncestorSidebarBox.js @@ -0,0 +1,27 @@ +export default { + contentDependencies: [ + 'generateArtTagAncestorDescendantMapList', + 'linkArtTagDynamically', + ], + + extraDependencies: ['html'], + + relations: (relation, ancestorArtTag, descendantArtTag) => ({ + ancestorArtTagLink: + relation('linkArtTagDynamically', ancestorArtTag), + + ancestorArtTagMapList: + relation('generateArtTagAncestorDescendantMapList', + ancestorArtTag, + descendantArtTag), + }), + + generate: (relations, {html}) => ({ + content: html.tags([ + html.tag('h2', + relations.ancestorArtTagLink), + + relations.ancestorArtTagMapList, + ]), + }), +}; diff --git a/src/content/dependencies/generateArtTagInfoPage.js b/src/content/dependencies/generateArtTagInfoPage.js index 7b9d47b9..be25cd97 100644 --- a/src/content/dependencies/generateArtTagInfoPage.js +++ b/src/content/dependencies/generateArtTagInfoPage.js @@ -3,6 +3,7 @@ import {empty, unique} from '#sugar'; export default { contentDependencies: [ 'generateArtTagNavLinks', + 'generateArtTagSidebar', 'generateContentHeading', 'generatePageLayout', 'linkArtTagGallery', @@ -37,6 +38,9 @@ export default { relations.navLinks = relation('generateArtTagNavLinks', artTag); + relations.sidebar = + relation('generateArtTagSidebar', artTag); + const info = sec.info = {}; if (artTag.description) { @@ -180,10 +184,7 @@ export default { navLinkStyle: 'hierarchical', navLinks: relations.navLinks.content, - leftSidebarMultiple: [ - {content: `I'm a sidebar.`}, - {content: `I am another sidebar.`}, - ], + ...relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generateArtTagSidebar.js b/src/content/dependencies/generateArtTagSidebar.js new file mode 100644 index 00000000..51e53d20 --- /dev/null +++ b/src/content/dependencies/generateArtTagSidebar.js @@ -0,0 +1,31 @@ +import {collectTreeLeaves} from '#wiki-data'; + +export default { + contentDependencies: ['generateArtTagAncestorSidebarBox'], + extraDependencies: ['wikiData'], + + sprawl: ({artTagData}) => + ({artTagData}), + + query(sprawl, artTag) { + const baobab = artTag.ancestorArtTagBaobabTree; + const uniqueLeaves = new Set(collectTreeLeaves(baobab)); + + // Just match the order in tag data. + const furthestAncestorArtTags = + sprawl.artTagData + .filter(artTag => uniqueLeaves.has(artTag)); + + return {furthestAncestorArtTags}; + }, + + relations: (relation, query, sprawl, artTag) => ({ + ancestorBoxes: + query.furthestAncestorArtTags + .map(ancestorArtTag => + relation('generateArtTagAncestorSidebarBox', ancestorArtTag, artTag)), + }), + + generate: (relations) => + ({leftSidebarMultiple: relations.ancestorBoxes}), +}; diff --git a/src/data/composite/things/art-tag/index.js b/src/data/composite/things/art-tag/index.js index 0c365ce2..bbd38293 100644 --- a/src/data/composite/things/art-tag/index.js +++ b/src/data/composite/things/art-tag/index.js @@ -1 +1,2 @@ export {default as withAllDescendantArtTags} from './withAllDescendantArtTags.js'; +export {default as withAncestorArtTagBaobabTree} from './withAncestorArtTagBaobabTree.js'; diff --git a/src/data/composite/things/art-tag/withAncestorArtTagBaobabTree.js b/src/data/composite/things/art-tag/withAncestorArtTagBaobabTree.js new file mode 100644 index 00000000..d5caa99e --- /dev/null +++ b/src/data/composite/things/art-tag/withAncestorArtTagBaobabTree.js @@ -0,0 +1,46 @@ +// Gets all the art tags which are ancestors of this one as a "baobab tree" - +// what you'd typically think of as roots are all up in the air! Since this +// really is backwards from the way that the art tag tree is written in data, +// chances are pretty good that there will be many of the exact same "leaf" +// nodes - art tags which don't themselves have any ancestors. In the actual +// data structure, each node is a Map, with keys for each ancestor and values +// for each ancestor's own baobab (thus a branching structure, just like normal +// trees in this regard). + +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withReverseReferenceList} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withAncestorArtTagBaobabTree`, + + outputs: ['#ancestorArtTagBaobabTree'], + + steps: () => [ + withReverseReferenceList({ + data: 'artTagData', + list: input.value('directDescendantArtTags'), + }).outputs({ + ['#reverseReferenceList']: '#directAncestorArtTags', + }), + + raiseOutputWithoutDependency({ + dependency: '#directAncestorArtTags', + mode: input.value('empty'), + output: input.value({'#ancestorArtTagBaobabTree': {}}) + }), + + { + dependencies: ['#directAncestorArtTags'], + compute: (continuation, { + ['#directAncestorArtTags']: directAncestorArtTags, + }) => continuation({ + ['#ancestorArtTagBaobabTree']: + new Map( + directAncestorArtTags + .map(artTag => [artTag, artTag.ancestorArtTagBaobabTree])), + }), + }, + ], +}); diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js index 707b3c93..a530ba8c 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -19,7 +19,8 @@ import { wikiData, } from '#composite/wiki-properties'; -import {withAllDescendantArtTags} from '#composite/things/art-tag'; +import {withAllDescendantArtTags, withAncestorArtTagBaobabTree} + from '#composite/things/art-tag'; import Thing from './thing.js'; @@ -110,5 +111,10 @@ export class ArtTag extends Thing { data: 'artTagData', list: input.value('directDescendantArtTags'), }), + + ancestorArtTagBaobabTree: [ + withAncestorArtTagBaobabTree(), + exposeDependency({dependency: '#ancestorArtTagBaobabTree'}), + ], }); } diff --git a/src/data/things/language.js b/src/data/things/language.js index fe74f7bf..cd719d0c 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -376,6 +376,7 @@ Object.assign(Language.prototype, { countAdditionalFiles: countHelper('additionalFiles', 'files'), countAlbums: countHelper('albums'), countArtworks: countHelper('artworks'), + countArtTags: countHelper('artTags', 'tags'), countFlashes: countHelper('flashes'), countCommentaryEntries: countHelper('commentaryEntries', 'entries'), countContributions: countHelper('contributions'), diff --git a/src/static/site4.css b/src/static/site4.css index 62659519..6dd19d84 100644 --- a/src/static/site4.css +++ b/src/static/site4.css @@ -296,6 +296,11 @@ body::before { margin: 0; } +.sidebar h2:first-child { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + .sidebar h3 { font-size: 1.1em; font-style: oblique; @@ -343,6 +348,10 @@ body::before { margin-left: 0; } +.sidebar dl > dd > dl { + padding-left: 15px; +} + .sidebar > dl .side { padding-left: 10px; } diff --git a/src/strings-default.json b/src/strings-default.json index 7f7a3646..be8939b8 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -29,6 +29,13 @@ "count.artworks.withUnit.few": "", "count.artworks.withUnit.many": "", "count.artworks.withUnit.other": "{ARTWORKS} artworks", + "count.artTags": "{TAGS}", + "count.artTags.withUnit.zero": "", + "count.artTags.withUnit.one": "{TAGS} tag", + "count.artTags.withUnit.two": "", + "count.artTags.withUnit.few": "", + "count.artTags.withUnit.many": "", + "count.artTags.withUnit.other": "{TAGS} tags", "count.commentaryEntries": "{ENTRIES}", "count.commentaryEntries.withUnit.zero": "", "count.commentaryEntries.withUnit.one": "{ENTRIES} entry", @@ -335,6 +342,7 @@ "artTagGalleryPage.infoLine": "Appears in {COVER_ARTS}.", "artTagGalleryPage.descendsFrom": "Descends from {TAGS}.", "artTagGalleryPage.desendants": "Direct descendants: {TAGS}.", + "artTagSidebar.otherTagsExempt": "(…another {TAGS}…)", "commentaryIndex.title": "Commentary", "commentaryIndex.infoLine": "{WORDS} across {ENTRIES}, in all.", "commentaryIndex.albumList.title": "Choose an album:", diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index fecf94e1..a85dd9a2 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -97,6 +97,38 @@ export function chunkMultipleArrays(...args) { return results; } +// This (or its helper function) should probably be a generator, but generators +// are scary... Note that the root node is never considered a leaf, even if it +// doesn't have any branches. It does NOT pay attention to the *values* of the +// leaf nodes - it's suited to handle this kind of form: +// +// { +// foo: { +// bar: {}, +// baz: {}, +// qux: { +// woz: {}, +// }, +// }, +// } +// +// for which it outputs ['bar', 'baz', 'woz']. +// +export function collectTreeLeaves(tree) { + const recursive = ([key, value]) => + (value instanceof Map + ? (value.size === 0 + ? [key] + : Array.from(value.entries()).flatMap(recursive)) + : (empty(Object.keys(value)) + ? [key] + : Object.entries(value).flatMap(recursive))); + + const root = Symbol(); + const leaves = recursive([root, tree]); + return (leaves[0] === root ? [] : leaves); +} + // Sorting functions - all utils here are mutating, so make sure to initially // slice/filter/somehow generate a new array from input data if retaining the // initial sort matters! (Spoilers: If what you're doing involves any kind of -- cgit 1.3.0-6-gf8a5