diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-10-05 23:22:43 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2025-02-25 20:03:26 -0400 |
commit | 140a757cde7fdb1a72b56c5d39de713595c053ad (patch) | |
tree | 4b77582b88d57e39c19a092dada304bf46bfa56a | |
parent | 81f9eed9ac7c25bb9dd11a0e0a8c7bda83cd14bc (diff) |
content, data: experimental art tag sidebar
-rw-r--r-- | src/common-util/sugar.js | 32 | ||||
-rw-r--r-- | src/content/dependencies/generateArtTagAncestorDescendantMapList.js | 128 | ||||
-rw-r--r-- | src/content/dependencies/generateArtTagAncestorSidebarBox.js | 34 | ||||
-rw-r--r-- | src/content/dependencies/generateArtTagInfoPage.js | 7 | ||||
-rw-r--r-- | src/content/dependencies/generateArtTagSidebar.js | 40 | ||||
-rw-r--r-- | src/data/composite/things/art-tag/index.js | 1 | ||||
-rw-r--r-- | src/data/composite/things/art-tag/withAncestorArtTagBaobabTree.js | 46 | ||||
-rw-r--r-- | src/data/things/art-tag.js | 8 | ||||
-rw-r--r-- | src/data/things/language.js | 1 | ||||
-rw-r--r-- | src/static/css/site.css | 9 | ||||
-rw-r--r-- | src/strings-default.yaml | 13 |
11 files changed, 318 insertions, 1 deletions
diff --git a/src/common-util/sugar.js b/src/common-util/sugar.js index 89699f60..66e160aa 100644 --- a/src/common-util/sugar.js +++ b/src/common-util/sugar.js @@ -781,6 +781,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); +} + // Delicious function annotations, such as: // // (*bound) soWeAreBackInTheMine diff --git a/src/content/dependencies/generateArtTagAncestorDescendantMapList.js b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js new file mode 100644 index 00000000..30d4f4de --- /dev/null +++ b/src/content/dependencies/generateArtTagAncestorDescendantMapList.js @@ -0,0 +1,128 @@ +import {filterMultipleArrays, sortMultipleArrays, stitchArrays} from '#sugar'; + +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.$('artTagPage.sidebar.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..1f90a014 --- /dev/null +++ b/src/content/dependencies/generateArtTagAncestorSidebarBox.js @@ -0,0 +1,34 @@ +export default { + contentDependencies: [ + 'generateArtTagAncestorDescendantMapList', + 'generatePageSidebarBox', + 'linkArtTagDynamically', + ], + + extraDependencies: ['html'], + + relations: (relation, ancestorArtTag, descendantArtTag) => ({ + sidebarBox: + relation('generatePageSidebarBox'), + + ancestorArtTagLink: + relation('linkArtTagDynamically', ancestorArtTag), + + ancestorArtTagMapList: + relation('generateArtTagAncestorDescendantMapList', + ancestorArtTag, + descendantArtTag), + }), + + generate: (relations, {html}) => + relations.sidebarBox.slots({ + attributes: {class: 'tag-ancestor-sidebar-box'}, + + 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 55da4148..929aef07 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', @@ -32,6 +33,9 @@ export default { navLinks: relation('generateArtTagNavLinks', artTag), + sidebar: + relation('generateArtTagSidebar', artTag), + contentHeading: relation('generateContentHeading'), @@ -191,5 +195,8 @@ export default { navLinkStyle: 'hierarchical', navLinks: relations.navLinks.content, + + leftSidebar: + relations.sidebar, })), }; diff --git a/src/content/dependencies/generateArtTagSidebar.js b/src/content/dependencies/generateArtTagSidebar.js new file mode 100644 index 00000000..2c4b77b1 --- /dev/null +++ b/src/content/dependencies/generateArtTagSidebar.js @@ -0,0 +1,40 @@ +import {collectTreeLeaves} from '#sugar'; + +export default { + contentDependencies: [ + 'generateArtTagAncestorSidebarBox', + 'generatePageSidebar', + ], + + 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) => ({ + sidebar: + relation('generatePageSidebar'), + + ancestorBoxes: + query.furthestAncestorArtTags + .map(ancestorArtTag => + relation('generateArtTagAncestorSidebarBox', ancestorArtTag, artTag)), + }), + + generate: (relations) => + relations.sidebar.slots({ + boxes: 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..e084a42b --- /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'; +import {soupyReverse} from '#composite/wiki-properties'; + +export default templateCompositeFrom({ + annotation: `withAncestorArtTagBaobabTree`, + + outputs: ['#ancestorArtTagBaobabTree'], + + steps: () => [ + withReverseReferenceList({ + reverse: soupyReverse.input('artTagsWhichDirectlyAncestor'), + }).outputs({ + ['#reverseReferenceList']: '#directAncestorArtTags', + }), + + raiseOutputWithoutDependency({ + dependency: '#directAncestorArtTags', + mode: input.value('empty'), + output: input.value({'#ancestorArtTagBaobabTree': new Map()}), + }), + + { + 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 60a4340d..f023eb76 100644 --- a/src/data/things/art-tag.js +++ b/src/data/things/art-tag.js @@ -24,7 +24,8 @@ import { wikiData, } from '#composite/wiki-properties'; -import {withAllDescendantArtTags} from '#composite/things/art-tag'; +import {withAllDescendantArtTags, withAncestorArtTagBaobabTree} + from '#composite/things/art-tag'; export class ArtTag extends Thing { static [Thing.referenceType] = 'tag'; @@ -113,6 +114,11 @@ export class ArtTag extends Thing { directAncestorArtTags: reverseReferenceList({ reverse: soupyReverse.input('artTagsWhichDirectlyAncestor'), }), + + ancestorArtTagBaobabTree: [ + withAncestorArtTagBaobabTree(), + exposeDependency({dependency: '#ancestorArtTagBaobabTree'}), + ], }); static [Thing.findSpecs] = { diff --git a/src/data/things/language.js b/src/data/things/language.js index 800c4471..98ce12c6 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -896,6 +896,7 @@ const countHelper = (stringKey, optionName = stringKey) => Object.assign(Language.prototype, { countAdditionalFiles: countHelper('additionalFiles', 'files'), countAlbums: countHelper('albums'), + countArtTags: countHelper('artTags', 'tags'), countArtworks: countHelper('artworks'), countCommentaryEntries: countHelper('commentaryEntries', 'entries'), countContributions: countHelper('contributions'), diff --git a/src/static/css/site.css b/src/static/css/site.css index e38b5e0d..cb992d6c 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -342,6 +342,11 @@ body::before, .wallpaper-part { margin: 0; } +.sidebar h2:first-child { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + .sidebar h3 { font-size: 1.1em; font-style: oblique; @@ -389,6 +394,10 @@ body::before, .wallpaper-part { margin-left: 0; } +.sidebar dl > dd > dl { + padding-left: 15px; +} + .sidebar > dl .side { padding-left: 10px; } diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 386d1a42..4ef9624c 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -57,6 +57,16 @@ count: many: "" other: "{ALBUMS} albums" + artTags: + _: "{TAGS}" + withUnit: + zero: "" + one: "{TAGS} tag" + two: "" + few: "" + many: "" + other: "{TAGS} tags" + artworks: _: "{ARTWORKS}" withUnit: @@ -1272,6 +1282,9 @@ artTagPage: nav: tag: "Tag: {TAG}" + sidebar: + otherTagsExempt: "(…another {TAGS}…)" + # # artTagInfoPage: # The art tag info page displays general information about a tag, |