From b872d3bfbcced72abdc84050e4d8508825ec19b4 Mon Sep 17 00:00:00 2001
From: Julia Kent <46687291+jukent@users.noreply.github.com>
Date: Fri, 7 Jun 2024 08:26:33 -0600
Subject: [PATCH] Fix dropdown gallery filtering (#208)
* drop down menu without bullets
* css and remove ===
* make subtext black
* mess with javascript
* jv edits
* filtering working!
* fix card margins, tag titling, and clear all filters
* add all cookbooks back
* fix merge conflicts
* undo subtext changes
---
.../_extensions/cookbook_gallery_generator.py | 4 +-
site/_extensions/gallery_generator.py | 104 ++++-----
site/_static/custom.css | 46 ++++
site/_static/custom.js | 211 +++++-------------
site/conf.py | 2 +-
site/cookbook_gallery.txt | 2 +-
site/cookbook_gallery_subtext.md | 2 +-
7 files changed, 162 insertions(+), 209 deletions(-)
diff --git a/site/_extensions/cookbook_gallery_generator.py b/site/_extensions/cookbook_gallery_generator.py
index 6b56e92..9f36b0f 100644
--- a/site/_extensions/cookbook_gallery_generator.py
+++ b/site/_extensions/cookbook_gallery_generator.py
@@ -1,6 +1,5 @@
from gallery_generator import build_from_repos, generate_menu, generate_repo_dicts
-
def main(app):
with open("cookbook_gallery.txt") as fid:
@@ -25,6 +24,5 @@ def main(app):
repo_dicts, "index", title=title, subtext=subtext, menu_html=menu_html
)
-
def setup(app):
- app.connect("builder-inited", main)
+ app.connect("builder-inited", main)
\ No newline at end of file
diff --git a/site/_extensions/gallery_generator.py b/site/_extensions/gallery_generator.py
index 71701c7..00eb212 100644
--- a/site/_extensions/gallery_generator.py
+++ b/site/_extensions/gallery_generator.py
@@ -1,5 +1,6 @@
import itertools, json, yaml, pathlib, subprocess, requests
from truncatehtml import truncate
+import re
def _grab_binder_link(repo):
@@ -113,14 +114,18 @@ def _generate_sorted_tag_keys(repo_dicts):
return sorted(key_set)
+def _title_case_preserve(s):
+ return re.sub(r'\b(\w)', lambda m: m.group(1).upper(), s)
+
def _generate_tag_set(repo_dicts, tag_key=None):
tag_set = set()
for repo_dict in repo_dicts:
for k, e in repo_dict["tags"].items():
+ tags = [_title_case_preserve(t) for t in e]
if tag_key and k != tag_key:
continue
- for t in e:
+ for t in tags:
tag_set.add(t)
return tag_set
@@ -132,20 +137,18 @@ def _generate_tag_menu(repo_dicts, tag_key):
tag_list = sorted(tag_set)
options = "".join(
- f'
'
+ f''
for tag in tag_list
)
return f"""
-
-
-
-
-
+ :::{{dropdown}} {tag_key}
+
+ :::
"""
@@ -200,12 +203,11 @@ def build_from_repos(
tag_list = sorted((itertools.chain(*tag_dict.values())))
tag_list_f = [tag.replace(" ", "-") for tag in tag_list]
tags = [
- f'{tag}'
+ f'{_title_case_preserve(tag)}'
for tag in tag_list_f
]
tags = "\n".join(tags)
-
- # tag_class_str = " ".join(tag_list_f)
+ tag_classes = " ".join(tag_list_f)
description = repo_dict["description"]
ellipsis_str = ' ... more'
@@ -214,53 +216,53 @@ def build_from_repos(
if ellipsis_str in short_description:
modal_str = f"""
-
-
![]({thumbnail_url})
-
{cookbook_title}
- {authors_str}
-
{description}
-
{tags}
-
Visit Website
-
+
+
![]({thumbnail_url})
+
{cookbook_title}
+ {authors_str}
+
{description}
+
{tags}
+
Visit Website
+
"""
modal_str = '\n'.join([m.lstrip() for m in modal_str.split('\n')])
else:
modal_str = ""
-
- new_card = f"""\
- :::{{grid-item-card}}
- :shadow: md
- :class-footer: card-footer
-
-
![]({thumbnail_url})
-
-
- {modal_str}
-
- +++
-
-
-
- :::
-
- """
- grid_body.append('\n'.join([m.lstrip() for m in new_card.split('\n')]))
+ new_card = f"""
+ :::{{grid-item-card}}
+ :shadow: md
+ :class-footer: card-footer
+ :class-card: tagged-card {tag_classes}
+
+
+
![]({thumbnail_url})
+
+
+ {modal_str}
+
+ +++
+
+
+ :::
+ """
+ grid_body.append('\n'.join([m.lstrip() for m in new_card.split('\n')]))
- grid_body = "\n".join(grid_body)
stitle = f"#### {subtitle}" if subtitle else ""
stext = subtext if subtext else ""
+ grid_body = "\n".join(grid_body)
+
grid = f"""
# {title}
@@ -270,12 +272,12 @@ def build_from_repos(
{menu_html}
::::{{grid}} 1
- :gutter: 4
+ :gutter: 0
{grid_body}
-
+
"""
grid = '\n'.join([m.lstrip() for m in grid.split('\n')])
diff --git a/site/_static/custom.css b/site/_static/custom.css
index 97d45a9..d9e0cdb 100644
--- a/site/_static/custom.css
+++ b/site/_static/custom.css
@@ -153,3 +153,49 @@ main.banner-main #project-pythia {
.tagsandbadges {
padding: 0 0;
}
+
+.dropdown ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.dropdown-item {
+ display: block;
+}
+
+.dropdown-item input[type="checkbox"] {
+ margin-right: 0.5em;
+}
+
+details.sd-dropdown {
+ box-shadow: none !important;
+}
+
+details.sd-dropdown summary.sd-card-header + div.sd-summary-content {
+ background-color: white !important;
+ border: 0.2rem solid var(--pst-sd-dropdown-color) !important;
+ border-radius: calc(.25rem - 1px);
+}
+
+.bd-content .sd-card .sd-card-header {
+ background-color: var(--pst-color-panel-background) !important;
+}
+
+.sd-summary-content.sd-card-body.docutils {
+ position: absolute;
+ z-index: 100;
+}
+
+details.sd-dropdown summary.sd-card-header {
+ border: 0.2rem solid var(--pst-sd-dropdown-color) !important;
+ border-radius: calc(.25rem - 1px);
+}
+
+p {
+ color: black;
+}
+
+.sd-col.sd-d-flex-row.docutils.has-visible-card {
+ margin-bottom: 1rem;
+}
diff --git a/site/_static/custom.js b/site/_static/custom.js
index f0f3d57..26420cf 100644
--- a/site/_static/custom.js
+++ b/site/_static/custom.js
@@ -1,184 +1,91 @@
-var buttons = document.querySelectorAll('.modal-btn')
-var backdrop = document.querySelector('.modal-backdrop')
-var modals = document.querySelectorAll('.modal')
-
-function openModal(i) {
- backdrop.style.display = 'block'
- modals[i].style.display = 'block'
-}
-
-function closeModal(i) {
- backdrop.style.display = 'none'
- modals[i].style.display = 'none'
-}
-
-for (i = 0; i < buttons.length; i++) {
- buttons[i].addEventListener(
- 'click',
- (function (j) {
- return function () {
- openModal(j)
- }
- })(i)
- )
- backdrop.addEventListener(
- 'click',
- (function (j) {
- return function () {
- closeModal(j)
- }
- })(i)
- )
+function getClassOfCheckedCheckboxes(checkboxes) {
+ var tags = [];
+ checkboxes.forEach(function (cb) {
+ if (cb.checked) {
+ tags.push(cb.getAttribute("rel"));
+ }
+ });
+ return tags;
}
-
function change() {
- var affiliationCbs = document.querySelectorAll(".affiliation input[type='checkbox']");
+ console.log("Change event fired.");
var domainsCbs = document.querySelectorAll(".domains input[type='checkbox']");
- var formatsCbs = document.querySelectorAll(".formats input[type='checkbox']");
var packagesCbs = document.querySelectorAll(".packages input[type='checkbox']");
+ var domainTags = getClassOfCheckedCheckboxes(domainsCbs);
+ var packageTags = getClassOfCheckedCheckboxes(packagesCbs);
+
var filters = {
- affiliation: getClassOfCheckedCheckboxes(affiliationCbs),
- domains: getClassOfCheckedCheckboxes(domainsCbs),
- formats: getClassOfCheckedCheckboxes(formatsCbs),
- packages: getClassOfCheckedCheckboxes(packagesCbs)
+ domains: domainTags,
+ packages: packageTags
};
filterResults(filters);
}
-function getClassOfCheckedCheckboxes(checkboxes) {
- var classes = [];
-
- if (checkboxes && checkboxes.length > 0) {
- for (var i = 0; i < checkboxes.length; i++) {
- var cb = checkboxes[i];
-
- if (cb.checked) {
- classes.push(cb.getAttribute("rel"));
- }
- }
- }
-
- return classes;
-}
-
function filterResults(filters) {
+ console.log("Filtering results...");
var rElems = document.querySelectorAll(".tagged-card");
- var hiddenElems = [];
-
- if (!rElems || rElems.length <= 0) {
- return;
- }
-
- for (var i = 0; i < rElems.length; i++) {
- var el = rElems[i];
-
- if (filters.affiliation.length > 0) {
- var isHidden = true;
- for (var j = 0; j < filters.affiliation.length; j++) {
- var filter = filters.affiliation[j];
+ rElems.forEach(function (el) {
+ var isVisible = true; // Assume visible by default
- if (el.classList.contains(filter)) {
- isHidden = false;
- break;
- }
- }
+ // Check if the element has any domain or package filter
+ if (filters.domains.length > 0 || filters.packages.length > 0) {
+ var hasMatchingDomain = filters.domains.length === 0 || filters.domains.some(domain => el.classList.contains(domain));
+ var hasMatchingPackage = filters.packages.length === 0 || filters.packages.some(package => el.classList.contains(package));
- if (isHidden) {
- hiddenElems.push(el);
- }
+ // The element should be visible if it matches any filter within each category
+ isVisible = hasMatchingDomain && hasMatchingPackage;
}
- if (filters.domains.length > 0) {
- var isHidden = true;
-
- for (var j = 0; j < filters.domains.length; j++) {
- var filter = filters.domains[j];
-
- if (el.classList.contains(filter)) {
- isHidden = false;
- break;
- }
- }
-
- if (isHidden) {
- hiddenElems.push(el);
- }
+ // Toggle visibility based on the result
+ if (isVisible) {
+ el.classList.remove("d-none");
+ el.classList.add("d-flex");
+ } else {
+ el.classList.remove("d-flex");
+ el.classList.add("d-none");
}
+ });
- if (filters.formats.length > 0) {
- var isHidden = true;
-
- for (var j = 0; j < filters.formats.length; j++) {
- var filter = filters.formats[j];
-
- if (el.classList.contains(filter)) {
- isHidden = false;
- break;
- }
- }
-
- if (isHidden) {
- hiddenElems.push(el);
- }
- }
+ // Update the margins after filtering
+ updateMargins();
+}
- if (filters.packages.length > 0) {
- var isHidden = true;
+var checkboxes = document.querySelectorAll('input[type="checkbox"]');
+checkboxes.forEach(function (checkbox) {
+ checkbox.addEventListener("change", change);
+});
- for (var j = 0; j < filters.packages.length; j++) {
- var filter = filters.packages[j];
+function updateMargins() {
+ const columns = document.querySelectorAll('.sd-col.sd-d-flex-row.docutils');
- if (el.classList.contains(filter)) {
- isHidden = false;
- break;
- }
- }
+ columns.forEach(column => {
+ // Check if this column has any visible cards
+ const hasVisibleCard = Array.from(column.children).some(child => !child.classList.contains('d-none'));
- if (isHidden) {
- hiddenElems.push(el);
- }
+ // Toggle a class based on whether there are visible cards
+ if (hasVisibleCard) {
+ column.classList.add('has-visible-card');
+ } else {
+ column.classList.remove('has-visible-card');
}
- }
-
- for (var i = 0; i < rElems.length; i++) {
- rElems[i].classList.replace("d-none", "d-flex");
- }
-
- if (hiddenElems.length <= 0) {
- return;
- }
-
- for (var i = 0; i < hiddenElems.length; i++) {
- hiddenElems[i].classList.replace("d-flex", "d-none");
- }
+ });
}
-
function clearCbs() {
- var affiliationCbs = document.querySelectorAll(".affiliation input[type='checkbox']");
- var domainsCbs = document.querySelectorAll(".domains input[type='checkbox']");
- var formatsCbs = document.querySelectorAll(".formats input[type='checkbox']");
- var packagesCbs = document.querySelectorAll(".packages input[type='checkbox']");
+ // Select all checkbox inputs and uncheck them
+ var checkboxes = document.querySelectorAll('input[type="checkbox"]');
+ checkboxes.forEach(function(checkbox) {
+ checkbox.checked = false;
+ });
- for (var i = 0; i < affiliationCbs.length; i++) {
- affiliationCbs[i].checked=false;
- }
-
- for (var i = 0; i < domainsCbs.length; i++) {
- domainsCbs[i].checked=false;
- }
-
- for (var i = 0; i < formatsCbs.length; i++) {
- formatsCbs[i].checked=false;
- }
+ change();
+}
- for (var i = 0; i < packagesCbs.length; i++) {
- packagesCbs[i].checked=false;
- }
+// Initial call to set up correct margins when the page loads
+document.addEventListener('DOMContentLoaded', updateMargins);
- change();
-}
+console.log("Script loaded.");
diff --git a/site/conf.py b/site/conf.py
index f601cc2..c9deebe 100644
--- a/site/conf.py
+++ b/site/conf.py
@@ -75,7 +75,7 @@
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = ["custom.css"]
-# html_js_files = ['custom.js']
+html_js_files = ['custom.js']
# Disable Sidebars on special pages
html_sidebars = {
diff --git a/site/cookbook_gallery.txt b/site/cookbook_gallery.txt
index 23b6bb6..dfe4245 100644
--- a/site/cookbook_gallery.txt
+++ b/site/cookbook_gallery.txt
@@ -14,4 +14,4 @@ gridding-cookbook
vapor-python-cookbook
advanced-viz-cookbook
unstructured-grid-viz-cookbook
-na-cordex-viz-cookbook
+na-cordex-viz-cookbook
\ No newline at end of file
diff --git a/site/cookbook_gallery_subtext.md b/site/cookbook_gallery_subtext.md
index f43137f..891c24f 100644
--- a/site/cookbook_gallery_subtext.md
+++ b/site/cookbook_gallery_subtext.md
@@ -2,4 +2,4 @@ Pythia Cookbooks provide example workflows on more advanced and domain-specific
Cookbooks are created from Jupyter Notebooks that we strive to binderize so each Cookbook can be [executed in the cloud with a single click from your browser](https://foundations.projectpythia.org/preamble/how-to-use.html#interacting-with-jupyter-notebooks-in-the-cloud-via-binder), but in some instances executing a Cookbook will require [running the notebooks locally](https://foundations.projectpythia.org/preamble/how-to-use.html#interacting-with-jupyter-books-locally).
-Interested in contributing a new Cookbook or contributing to an existing Cookbook? Great! Please see the [Project Pythia Cookbook Contributor's Guide](https://projectpythia.org/cookbook-guide.html), and consider opening a discussion under the [Project Pythia category of the Pangeo Discourse](https://discourse.pangeo.io/c/education/project-pythia/60).
+Interested in contributing a new Cookbook or contributing to an existing Cookbook? Great! Please see the [Project Pythia Cookbook Contributor's Guide](https://projectpythia.org/cookbook-guide.html), and consider opening a discussion under the [Project Pythia category of the Pangeo Discourse](https://discourse.pangeo.io/c/education/project-pythia/60).
\ No newline at end of file