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""" """ 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 - - {modal_str} - - +++ - -
    - {tags} -
    {status_badges}
    -
    - - ::: - - """ - 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} + + + {modal_str} + + +++ + +
    + {tags} +
    {status_badges}
    +
    + ::: + """ + 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