diff --git a/docs/browse/classes.js b/docs/browse/classes.js index e41343a..227bf25 100644 --- a/docs/browse/classes.js +++ b/docs/browse/classes.js @@ -66,6 +66,8 @@ window.addEventListener("hashchange", () => { renderCurrentClass(); }); +let elasticListData = []; + loadClasses(); async function loadClasses() { @@ -82,6 +84,7 @@ async function loadClasses() { } renderClassTree(classes); + loadElasticListData(); if (!getClassUriFromLocation()) { hideStatus(); @@ -516,3 +519,224 @@ function showStatus(message) { function hideStatus() { statusEl.hidden = true; } + +function queryElasticData() { + var query = ` + PREFIX grace: + PREFIX rdfs: + + SELECT DISTINCT ?event ?eventLabel ?personLabel ?placeLabel ?instLabel ?typeLabel ?dateLabel WHERE { + ?event grace:apostolic_provision . + ?event rdfs:label ?eventLabel . + OPTIONAL { ?event grace:refers_to ?person . ?person grace:person . ?person rdfs:label ?personLabel . } + OPTIONAL { ?event grace:place_of_event ?place . ?place rdfs:label ?placeLabel . } + OPTIONAL { ?event grace:institution_object ?inst . ?inst rdfs:label ?instLabel . } + OPTIONAL { ?event grace:type_object ?type . ?type rdfs:label ?typeLabel . } + OPTIONAL { ?event grace:event_date ?dateNode . ?dateNode grace:called ?dateLabel . } + } LIMIT 2000 + `; + return sparql(query); +} + +var ELASTIC_PAGE_SIZE = 12; +var elasticCurrentPage = 0; +var elasticFilteredData = []; +var elasticListInstance = null; + +var elasticResultsEl = document.getElementById("elastic-results-row"); +var elasticResetBtn = document.getElementById("elastic-reset"); +var elasticPrevBtn = document.getElementById("elastic-prev"); +var elasticNextBtn = document.getElementById("elastic-next"); +var elasticPageInfo = document.getElementById("elastic-page-info"); +var elasticResultCount = document.getElementById("elastic-result-count"); + +elasticResetBtn.addEventListener("click", resetElasticFilters); +elasticPrevBtn.addEventListener("click", function () { + if (elasticCurrentPage > 0) { + elasticCurrentPage--; + renderElasticPage(); + } +}); +elasticNextBtn.addEventListener("click", function () { + var totalPages = Math.ceil(elasticFilteredData.length / ELASTIC_PAGE_SIZE); + if (elasticCurrentPage < totalPages - 1) { + elasticCurrentPage++; + renderElasticPage(); + } +}); + +function resetElasticFilters() { + var builder = elasticListInstance.builder; + builder.el.find("li.active").removeClass("active"); + builder.el.find("li").removeClass(builder.hideClass); + for (var elId in builder.countMap) { + builder.el.find("#" + elId).text(builder.countMap[elId]); + } + builder.el.find("input.elastic-filter").val(""); + builder.el.find("style").html(""); + builder.onchange({}); +} + +function renderElasticPage() { + var totalPages = Math.max(1, Math.ceil(elasticFilteredData.length / ELASTIC_PAGE_SIZE)); + var start = elasticCurrentPage * ELASTIC_PAGE_SIZE; + var end = Math.min(start + ELASTIC_PAGE_SIZE, elasticFilteredData.length); + var pageItems = elasticFilteredData.slice(start, end); + + elasticResultsEl.innerHTML = ""; + + pageItems.forEach(function (item) { + elasticResultsEl.appendChild(createEventCard(item)); + }); + + elasticPrevBtn.disabled = elasticCurrentPage === 0; + elasticNextBtn.disabled = elasticCurrentPage >= totalPages - 1; + elasticPageInfo.textContent = "Page " + (elasticCurrentPage + 1) + " of " + totalPages; + elasticResultCount.textContent = elasticFilteredData.length + " results"; +} + +function showElasticLoading() { + var listEl = document.getElementById("elastic-list"); + listEl.innerHTML = '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; +} + +async function loadElasticListData() { + showElasticLoading(); + + try { + var results = await queryElasticData(); + var bindings = results.results.bindings; + + elasticListData = bindings.map(function (b) { + var dateLabel = b.dateLabel ? b.dateLabel.value : ""; + var year = ""; + var m = dateLabel.match(/(\d{4})/); + if (m) year = m[1]; + + return { + event_uri: b.event ? b.event.value : "", + event_label: b.eventLabel ? b.eventLabel.value : "", + person_label: b.personLabel ? b.personLabel.value : null, + place_label: b.placeLabel ? b.placeLabel.value : null, + inst_label: b.instLabel ? b.instLabel.value : null, + type_label: b.typeLabel ? b.typeLabel.value : null, + year_label: year + }; + }); + + document.getElementById("elastic-list").innerHTML = ""; + + elasticListInstance = new ElasticList({ + el: $("#elastic-list"), + data: elasticListData, + hasFilter: true, + align: "horizontal", + columns: [ + { title: "Person", attr: "person_label" }, + { title: "Institution", attr: "inst_label" }, + { title: "Place", attr: "place_label" }, + { title: "Type", attr: "type_label" }, + { title: "Year", attr: "year_label" } + ], + onchange: function (filters) { + elasticResultsEl.innerHTML = ""; + + if (Object.keys(filters).length === 0) { + elasticFilteredData = []; + elasticCurrentPage = 0; + elasticPrevBtn.disabled = true; + elasticNextBtn.disabled = true; + elasticPageInfo.textContent = ""; + elasticResultCount.textContent = ""; + return; + } + + elasticCurrentPage = 0; + + elasticFilteredData = elasticListData.filter(function (item) { + return Object.entries(filters).every(function (_ref) { + var key = _ref[0]; + var value = _ref[1]; + return String(item[key] || "").toLowerCase() === String(value).toLowerCase(); + }); + }); + + renderElasticPage(); + } + }); + + } catch (error) { + var listEl = document.getElementById("elastic-list"); + listEl.innerHTML = '

Failed to load browse data.

'; + console.error("Elastic list data error:", error); + } +} + +function createEventCard(item) { + var container = document.createElement("div"); + container.className = "col-md-4 mb-4"; + + var card = document.createElement("div"); + card.className = "card h-100"; + + var cardBody = document.createElement("div"); + cardBody.className = "card-body"; + + var title = document.createElement("h5"); + title.className = "card-title"; + var link = document.createElement("a"); + link.href = RESOURCE_VIEWER + "#" + encodeURIComponent(item.event_uri); + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = item.event_label; + link.className = "text-decoration-none"; + title.appendChild(link); + + cardBody.appendChild(title); + + if (item.person_label) { + var p = document.createElement("p"); + p.className = "card-text small"; + p.textContent = "Person: " + item.person_label; + cardBody.appendChild(p); + } + + if (item.inst_label) { + var p = document.createElement("p"); + p.className = "card-text small"; + p.textContent = "Institution: " + item.inst_label; + cardBody.appendChild(p); + } + + if (item.place_label) { + var p = document.createElement("p"); + p.className = "card-text small"; + p.textContent = "Place: " + item.place_label; + cardBody.appendChild(p); + } + + if (item.type_label) { + var p = document.createElement("p"); + p.className = "card-text small text-muted"; + p.textContent = "Type: " + item.type_label; + cardBody.appendChild(p); + } + + if (item.year_label) { + var p = document.createElement("p"); + p.className = "card-text small text-muted"; + p.textContent = "Year: " + item.year_label; + cardBody.appendChild(p); + } + + card.appendChild(cardBody); + container.appendChild(card); + + return container; +} diff --git a/docs/browse/elastic-list.css b/docs/browse/elastic-list.css new file mode 100644 index 0000000..8eb1c8f --- /dev/null +++ b/docs/browse/elastic-list.css @@ -0,0 +1,44 @@ +.list-container ul { + max-height: 370px; + overflow-y: auto; } + +.list-container .list-group-item { + background-color: white; + padding-right: 15px; + font-size: 12px; + padding-top: 5px; + height: 30px; + cursor: pointer; + -webkit-transition: height 0.25s, padding-top 0.25s, background-color 0.25s; + transition: height 0.25s, padding-top 0.25s, background-color 0.25s; } + .list-container .list-group-item div.elastic-data { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-top: -15px; + padding-right: 40px; } + .list-container .list-group-item div { + display: block; } +.list-container .list-group-item.active { + background: #337ab7; } + +.list-group-item.hide-element { + background-color: #f4f1f1; + height: 3px !important; + overflow: hidden; + padding: 0px; } + +.list-group-item.hide-element div { + display: none; } + +.list-container .hide-element { + cursor: not-allowed; } + +.elastic-filter { + width: 100%; } + +.badge { + color: black !important; + text-align: right; } + +/*# sourceMappingURL=elastic-list.css.map */ diff --git a/docs/browse/elastic-list.js b/docs/browse/elastic-list.js new file mode 100644 index 0000000..59ba570 --- /dev/null +++ b/docs/browse/elastic-list.js @@ -0,0 +1 @@ +var ElasticBuilder=function(){this.grafo={},this.countMap={},this.panelTemplate='
',this.panelHeadTemplate='
',this.panelBodyTemplate='
',this.hideClass="hide-element"};ElasticBuilder.prototype.initialize=function(options){for(var attr in options)this[attr]=options[attr];this.buildContainer(),this.buildList(),this.bindEvents(),this.setSelected()},ElasticBuilder.prototype.beforeSearch=function(value,columnAttr){return value},ElasticBuilder.prototype.genId=function(value){value=value.toString(),value=value.trim().toLowerCase();var i,chr,len,hash=0;if(0==value.length)return hash;for(i=0,len=value.length;i=0||(clazz.indexOf("active")>=0?($target.removeClass("active"),undo=!0):($target.addClass("active"),$target.removeClass(this.hideClass)),void 0!==this.hasFilter&&this.hasFilter&&this.el.find("input").each(function(){$(this).val(""),thiz.cleanFilters()}),this.applyFilters($target,undo))},ElasticBuilder.prototype.setSelected=function(options){var args=void 0===options?this.defaultValue:options;if(void 0!==args)for(var attr in args){var value=args[attr];value=this.parseValue(value);var key=value.toString().toLowerCase(),elKey=this.getKey(attr,key),$target=$("#"+elKey);$target.length>0&&(this.settingDefault=!0,$target.parent().click())}},ElasticBuilder.prototype.buildContainer=function(){var len="vertical"===this.align?12:parseInt(12/this.columns.length),$container=$("
"),$input=$(""),$panel=$(this.panelTemplate);$container.addClass("col-md-"+len);for(var $head=$(this.panelHeadTemplate),j=($(this.panelBodyTemplate),0);j");if($ul.attr("id",this.genColumnId(attr)),$ul.attr("data-name",attr),$ulPanel.append($ulhead),void 0!==this.hasFilter&&this.hasFilter){var $ulFilter=$input.clone();$ulFilter.attr("placeholder",this.columns[j].title),$ulPanel.append($ulFilter);var $style=$("");$style.attr("class",this.genColumnId(attr)),$ulPanel.append($style)}$ulPanel.append($ul),$ulContainer.append($ulPanel),this.el.append($ulContainer)}},ElasticBuilder.prototype.count=function(countMap,key,node){var hasCountColumn=void 0!==this.countColumn;return void 0===countMap[key]?(countMap[key]=hasCountColumn?node[this.countColumn]:1,countMap[key]):(countMap[key]+=hasCountColumn?node[this.countColumn]:1,countMap[key])},ElasticBuilder.prototype.buildList=function(){for(var j=0;jmaxCount&&(maxCount=counter)}for(var i=0;i"),$span=$("
"),$badge=$("
");$badge.addClass("badge"),$badge.attr("id",elKey),$badge.text(count),badgeMap[elKey]=$badge,$li.attr("data-value",key),"function"==typeof this.columns[j].formatter&&(value=this.columns[j].formatter(value,this.data[i]));var tmpValue=this.beforeSearch(value.toString().toLowerCase());$li.attr("data-formatted",tmpValue),$span.text(value),$span.addClass("elastic-data");var minHeight=26,maxHeight=80,height=minHeight+count/maxCount*(maxHeight-minHeight);$li.css("height",height+"px"),$li.append($badge),$li.append($span),tempLists[attr]||(tempLists[attr]=[]),tempLists[attr].push($li)}this.grafo[elKey].push(this.data[i])}for(var j=0;j0){data=data.toLocaleLowerCase(),data=this.beforeSearch(data,columnAttr);var rule=" ul#"+columnId+' li[data-formatted*="'+data+'"]{display:block;} ';rule+=" ul#"+columnId+' li:not([data-formatted*="'+data+'"]){display:none;}',$style.html(rule)}else $style.html("")},ElasticBuilder.prototype.cleanFilters=function(){this.el.find("style").each(function(){$(this).html("")})};var ElasticList=function(options){this.builder=new ElasticBuilder(options),this.builder.initialize(options)};ElasticList.prototype.getFilters=function(){return this.builder.getFilters()},ElasticList.prototype.setSelected=function(options){return this.builder.setSelected(options)},ElasticList.prototype.clean=function(options){var slecteds=this.getFilters();this.builder.setSelected(slecteds),this.builder.settingDefault=!1}; \ No newline at end of file diff --git a/docs/browse/index.html b/docs/browse/index.html index 7ff5e0e..d6fee0f 100644 --- a/docs/browse/index.html +++ b/docs/browse/index.html @@ -4,7 +4,9 @@ GRACEFUL17 Browser + + @@ -19,6 +21,24 @@

+
+

Browse Data

+
+
+ + +
+ + + +
+
+
+
+
+
+
+
Loading Classes …
- + + diff --git a/docs/browse/styles.css b/docs/browse/styles.css index 1678217..e0e2020 100644 --- a/docs/browse/styles.css +++ b/docs/browse/styles.css @@ -370,6 +370,40 @@ details table { .html-literal p:last-child { margin-bottom: 0; } + +#elastic-section { + background: var(--panel); + border: 2px solid var(--border-mid); + border-left: 8px solid var(--accent); + padding: 1.25rem 1.5rem; + margin-bottom: 1rem; +} + +#elastic-section h3 { + margin: 0 0 0.75rem 0; + font-size: 1.1rem; + font-weight: 700; + color: var(--border-dark); +} + +.card { + border: 2px solid var(--border-soft); + background: var(--bg); +} + +.card-body { + padding: 1rem; +} + +.card-title { + margin: 0 0 0.5rem 0; + font-size: 1.1rem; +} + +.card-text { + margin: 0.25rem 0; + font-size: 0.9rem; +} .layout { display: grid; grid-template-columns: 280px 1fr; @@ -421,6 +455,89 @@ details table { margin-top: 0; } +.elastic-toolbar { + display: flex; + align-items: center; + gap: 1rem; + margin-top: 0.75rem; + padding: 0.5rem 0; + flex-wrap: wrap; +} + +.elastic-toolbar button { + padding: 0.4rem 0.8rem; + border: 2px solid var(--border-mid); + background: var(--panel); + color: var(--border-dark); + font: inherit; + cursor: pointer; +} + +.elastic-toolbar button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.elastic-toolbar button:hover:not(:disabled) { + background: var(--panel-2); +} + +.elastic-result-count { + color: var(--muted); + font-size: 0.9rem; +} + +.elastic-pagination { + margin-left: auto; + display: flex; + align-items: center; + gap: 0.5rem; +} + +#elastic-page-info { + font-size: 0.85rem; + color: var(--muted); +} + +/* --- Skeleton loading --- */ +@keyframes shimmer { + 0% { background-position: -400px 0; } + 100% { background-position: 400px 0; } +} + +.elastic-list-skeleton { + display: flex; + gap: 1rem; + min-height: 200px; +} + +.elastic-list-skeleton .sk-col { + flex: 1; + border: 2px solid var(--border-soft); + background: var(--bg); + padding: 0.6rem; +} + +.elastic-list-skeleton .sk-head { + height: 1rem; + margin-bottom: 0.6rem; + background: linear-gradient(90deg, var(--border-soft) 25%, var(--panel-3) 50%, var(--border-soft) 75%); + background-size: 800px 100%; + animation: shimmer 1.5s ease-in-out infinite; +} + +.elastic-list-skeleton .sk-item { + height: 0.7rem; + margin-bottom: 0.45rem; + background: linear-gradient(90deg, var(--border-soft) 25%, var(--panel-3) 50%, var(--border-soft) 75%); + background-size: 800px 100%; + animation: shimmer 1.5s ease-in-out infinite; +} + +.elastic-list-skeleton .sk-item:last-child { + margin-bottom: 0; +} + @media (max-width: 900px) { .layout { display: block;