diff --git a/trove/openapi.py b/trove/openapi.py index 0f6549738..954a0bff9 100644 --- a/trove/openapi.py +++ b/trove/openapi.py @@ -6,6 +6,7 @@ from primitive_metadata import primitive_rdf from share.version import __version__ +from trove.util.randomness import shuffled from trove.vocab.jsonapi import JSONAPI_MEMBERNAME, JSONAPI_MEDIATYPE from trove.vocab.namespaces import TROVE, RDFS, RDF, DCTERMS from trove.vocab.trove import TROVE_API_VOCAB @@ -30,7 +31,7 @@ def get_trove_openapi() -> dict: ''' # TODO: language parameter, get translations _api_graph = primitive_rdf.RdfGraph(TROVE_API_VOCAB) - _path_iris = set(_api_graph.q(TROVE.search_api, TROVE.hasPath)) + _path_iris = shuffled(set(_api_graph.q(TROVE.search_api, TROVE.hasPath))) _label = next(_api_graph.q(TROVE.search_api, RDFS.label)) _comment = next(_api_graph.q(TROVE.search_api, RDFS.comment)) return { @@ -68,7 +69,7 @@ def _openapi_parameters(path_iris: Iterable[str], api_graph: primitive_rdf.RdfGr api_graph.q(_path_iri, TROVE.hasParameter) for _path_iri in path_iris ))) - for _param_iri in _param_iris: + for _param_iri in shuffled(_param_iris): # TODO: better error message on absence try: _jsonname = next(api_graph.q(_param_iri, JSONAPI_MEMBERNAME)) @@ -134,8 +135,8 @@ def _openapi_path(path_iri: str, api_graph: primitive_rdf.RdfGraph): except StopIteration: raise ValueError(f'could not find trove:iriPath for {path_iri}') _label = ' '.join(_text(path_iri, RDFS.label, api_graph)) - _param_labels = list(_text(path_iri, {TROVE.hasParameter: {JSONAPI_MEMBERNAME}}, api_graph)) - _example_labels = list(_text(path_iri, {TROVE.example: {RDFS.label}}, api_graph)) + _param_labels = shuffled(_text(path_iri, {TROVE.hasParameter: {JSONAPI_MEMBERNAME}}, api_graph)) + _example_labels = shuffled(_text(path_iri, {TROVE.example: {RDFS.label}}, api_graph)) return _path, { 'get': { # TODO (if generalizability): separate metadata by verb # 'tags': diff --git a/trove/render/html_browse.py b/trove/render/html_browse.py index 9cd638e32..e4c799490 100644 --- a/trove/render/html_browse.py +++ b/trove/render/html_browse.py @@ -2,7 +2,6 @@ import datetime import markdown2 import random -from typing import Iterable from urllib.parse import quote, urlsplit, urlunsplit from xml.etree.ElementTree import ( Element, @@ -16,8 +15,9 @@ from primitive_metadata import primitive_rdf from trove.util.iris import get_sufficiently_unique_iri +from trove.util.randomness import shuffled from trove.vocab.jsonapi import JSONAPI_MEDIATYPE -from trove.vocab.namespaces import TROVE, RDF +from trove.vocab.namespaces import TROVE, RDF, FOAF from trove.vocab.trove import trove_browse_link from ._base import BaseRenderer @@ -63,7 +63,7 @@ def __render_mediatype_links(self): with self.__nest('nav', attrs={'class': 'VisibleNest Browse__card'}): self.__leaf('header', text='alternate mediatypes') with self.__nest('ul', attrs={'class': 'Browse__twopleset'}): - for _mediatype in _shuffled((*STABLE_MEDIATYPES, *UNSTABLE_MEDIATYPES)): + for _mediatype in shuffled((*STABLE_MEDIATYPES, *UNSTABLE_MEDIATYPES)): with self.__nest('li', attrs={'class': 'VisibleNest Browse__twople'}): self.__mediatype_link(_mediatype) @@ -87,7 +87,7 @@ def __mediatype_link(self, mediatype: str): _link.text = 'documented use' _link.tail = ')' - def __render_subj(self, subj_iri: str, twopledict=None): + def __render_subj(self, subj_iri: str, twopledict=None, start_collapsed=False): _twopledict = ( self.data.get(subj_iri, {}) if twopledict is None @@ -95,8 +95,15 @@ def __render_subj(self, subj_iri: str, twopledict=None): ) with self.__visiting(subj_iri): with self.__h_tag() as _h_tag: - with self.__nest('article', attrs={'class': 'Browse__card'}, visible=True): - with self.__nest('header'): + with self.__nest( + 'details', + attrs={ + 'class': 'Browse__card', + **({} if start_collapsed else {'open': ''}), + }, + visible=True, + ): + with self.__nest('summary'): _label = self.__label_for_iri(subj_iri) with self.__nest(_h_tag, attrs={'class': 'Browse__heading'}): with self.__nest_link(subj_iri): @@ -110,7 +117,7 @@ def __render_subj(self, subj_iri: str, twopledict=None): def __twoples(self, twopledict: primitive_rdf.RdfTwopleDictionary): with self.__nest('ul', {'class': 'Browse__twopleset'}): - for _pred, _obj_set in _shuffled(twopledict.items()): + for _pred, _obj_set in shuffled(twopledict.items()): with self.__nest('li', {'class': 'Browse__twople'}, visible=True): self.__leaf_link(_pred) # TODO: use a vocab, not static property iris @@ -118,10 +125,10 @@ def __twoples(self, twopledict: primitive_rdf.RdfTwopleDictionary): isinstance(_obj, primitive_rdf.QuotedTriple) for _obj in _obj_set ): - _focus_iris = twopledict[TROVE.focusIdentifier] # assumed + _focus_iris = twopledict[FOAF.primaryTopic] # assumed _focus_iri = None _quoted_triples = set() - for _obj in _shuffled(_obj_set): + for _obj in shuffled(_obj_set): _quoted_triples.add(_obj) (_subj, _, _) = _obj if _subj in _focus_iris: @@ -130,7 +137,7 @@ def __twoples(self, twopledict: primitive_rdf.RdfTwopleDictionary): self.__quoted_graph(_focus_iri, _quoted_triples) else: with self.__nest('ul', {'class': 'Browse__objectset'}): - for _obj in _shuffled(_obj_set): + for _obj in shuffled(_obj_set): with self.__nest('li', {'class': 'Browse__object'}, visible=True): self.__obj(_obj) @@ -184,7 +191,7 @@ def __quoted_graph(self, focus_iri, quoted_triples): for _triple in quoted_triples: _quoted_graph.add(_triple) with self.__quoted_data(_quoted_graph.tripledict): - self.__render_subj(focus_iri) + self.__render_subj(focus_iri, start_collapsed=True) ### # private html-building helpers @@ -275,9 +282,3 @@ def __label_for_iri(self, iri: str): if _shorthand == iri else _shorthand ) - - -def _shuffled(items: Iterable): - _item_list = list(items) - random.shuffle(_item_list) - return _item_list diff --git a/trove/static/css/browse.css b/trove/static/css/browse.css index 606edec1c..fb2714c21 100644 --- a/trove/static/css/browse.css +++ b/trove/static/css/browse.css @@ -60,6 +60,7 @@ .Browse__twople { display: flex; flex-direction: row; + align-items: flex-start; gap: 0.382rem; margin: 0; border: solid 1px rgba(0,0,0,0.382); diff --git a/trove/util/randomness.py b/trove/util/randomness.py new file mode 100644 index 000000000..ae93d0a83 --- /dev/null +++ b/trove/util/randomness.py @@ -0,0 +1,11 @@ +import random +import typing + + +_T = typing.TypeVar('_T') + + +def shuffled(items: typing.Iterable[_T]) -> list[_T]: + _itemlist = list(items) + random.shuffle(_itemlist) + return _itemlist