Making the Gallery

My initial plan was to generate the gallery using templates, like how making post collections and menus and such are typically done with a site generator. I was able to make it happen in the previous version of the site, but it wasn't ideal. I had to do some workarounds to get the separate sections to work with the lightbox, and overall it was more complicated than it needed to be. When it came to moving over to Eleventy, I didn't want to have to mess with templates again. I tried rewriting it to do everything in javascript rather than the templates, but eventually it dawned on me that I was at cross purposes: trying to generate the HTML at the same time as the javascript to interact with the HTML itself. It would make more sense to just generate a static HTML file first rather than try to do everything at once. So everything to do with loading JSON data and iterating for each image I shuffled off into a python script, leaving the lightbox and slide controls to javascript. It's then just a matter of running the script to regenerate the HTML file as needed.

gallery.py

import json
import html

# gallery structure:
# { gallery:        top level must be a dict
# └ [               list containing gallery sections
#  └ {              dict of section data
#  └ 'title':       section title -> header
#  └ 'images': [    list of section images
#   └ {             dict of image properties
#   └ 'picdate':    
#   └ 'name':       
#   └ 'thumb':      
#   └ 'img':        

# load JSON file as python dictionary
with open("_data/gallery.json") as f:
  data = json.load(f)

# creates HTML file or overwrites existing file
with open("gallery.html", "w") as f:
# frontmatter and thumbnail container
  f.write("""---
layout: base.njk
---
<div id='thumbnails'>
<div><h2>Gallery</h2>""")
# generate gallery
for gallerySections in data.values():
  sectionCount = 0  # to make thumbnail IDs
  slide = 0         # to open corresponding slide
  for section in gallerySections:
    sectionCount += 1
    imgCount = 0    # to make thumbnail IDs

# section headers
    sectionTitle = section['title']
    headerHTML = f"""
  </div>
  <h3>{sectionTitle}</h3><br>
  <div class='thumbnail-container'>"""
    with open("gallery.html", "a") as f:
      f.write(headerHTML)

# thumbnails
    for image in section['images']:
      imgCount += 1
      ID = f"pic{sectionCount}_{imgCount}"
      date = image['picdate']
      name = image['name']
      thumb = image['thumb']
      img = image['img']
      thumbnailHTML = f"""
      <div class='thumb'>
      <img src='{thumb}' alt='{name}' onclick='openLightbox();changeSlide({slide});'>
      </div>"""
      with open("gallery.html", "a") as f:
        f.write(thumbnailHTML)
      slide += 1

# lightbox container and controls
with open("gallery.html", "a") as f:
  f.write("""
</div>
<div id='lightbox'>
  <button class='close' onclick='closeLightbox();'>&times;</button>
  <button class='prev' onclick="slideNav(-1);">&#10094;</button>
  <button class='next' onclick="slideNav(1);">&#10095;</button>""")

# generate slides  
for gallerySections in data.values():
  sectionCount = 0
  for section in gallerySections:
    sectionCount += 1
    imgCount = 0                    # for slide number display
    sectionTitle = section['title']
    total = len(section['images'])  # for slide number display
    for image in section['images']:
      imgCount += 1
      ID = f"pic{sectionCount}_{imgCount}"
      date = image['picdate']
      name = image['name']
      thumb = image['thumb']
      img = image['img']
      slideHTML = f"""
  <div class='slides' id={ID}> 
    <figure> <img src='{img}'>
      <figcaption>
        <span class='section'>{sectionTitle}</span>
        <span class='slidecount'>{imgCount}/{total}</span>
        <p class='pictitle'>{name}</p>
        <p class='imglink'><a href='{img}'>click for original size</a></p>
        <p class='picdate'>{date}</p>
      </figcaption>
    </figure>
  </div>"""
      with open("gallery.html", "a") as f:
        f.write(slideHTML)

# closing tags and external javascript
# place script at end so HTML loads first
with open("gallery.html", "a") as f:
  f.write("""
</div>
<script src="/js/gallery.js"></script>""")  

gallery.js

// note that slide list is an HTMLCollection, doesn't use array methods
let slides = document.getElementsByClassName("slides");
let current= "";  // index number of slide to be displayed
let last= slides.length - 1;  // index starts at 0, ends at length - 1

function slideNav(n) {  // n = -1 for previous, n = 1 for next
  let next = current + n;
  if (next >= last) { next = 0 } // loop back to 0 after last slide
  if (next < 0) { next = last}  // loop back to end before first slide
  changeSlide(next);
}

function changeSlide(n) { 
  current = n;
  let currentSlide = slides.item(current);  // get HTML of slide from collection
  for (i = 0; i < slides.length; i++) {
    slides[i].style.display = "none";       // any open slide gets hidden
  }
  currentSlide.style.display = "block";     // only current slide made visible
}

// toggle lightbox visibility
function openLightbox() {
  document.getElementById("lightbox").style.display = "block";
}

function closeLightbox() {
  document.getElementById("lightbox").style.display = "none";
}

gallery.css

#lightbox {
  display: none;
  position: fixed;
  z-index: 1;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: var(--ink);
  background-color: var(--ink-transparent);
}

.thumbnail-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, 100px);
  gap: 8px;
  justify-content: center;
}

.thumb {
  border: 2px solid var(--accent1);
  background-color: var(--accent2);
  width: 100px;
  height: 100px;
  margin: 0;
  box-sizing: content-box;
}

.thumb img { cursor: pointer; }

.thumb img:hover { 
  opacity: 0.5;
}

.slides {
  display: none;
  position: fixed;
  left: 10%;
  width: 80%;
}

.slides img {
  max-width: 100%;
  max-height: 70vmin;
  height: auto;
  margin-left: auto;
  margin-right: auto;
  display: block;
}

.slides figcaption {
  display: grid;
  grid-template-columns: 1fr 5fr 1fr;
  border-radius: 10px;
  column-gap: 50px;
  background-color: var(--paper);
  font-size: 0.8rem;
  color: var(--bg);
  padding: 0rem 1rem;
  max-width: 100%;
  max-height: 70vmin;
}

.section {
  grid-row-start: 1;
  grid-column-start: 1;
}

.slidecount {
  grid-row-start: 1;
  grid-column-start: 2;
}

.pictitle {
  font-size: 1rem;
  color: var(--ink);
  grid-row-start: 2;
  grid-column-start: 2;
}

.imglink {
  grid-row-start: 3;
  grid-column: 1 / span 2;
}

.imglink a {
  color: var(--bg);
}

.picdate {
  grid-row-start: 3;
  grid-column-start: 3;
}

.close, .prev, .next {
  background-color: var(--paper);
  position: fixed;
  z-index: 2;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  border: none;
  width: 50px;
  height: 50px;
}

.close {
  border-radius: 50%;
  right: 10%;
  font-size: 1.5rem;
}

.prev {
  border-radius: 50% 0 0 50%;
  top: 50%;
  left: 10%;
}

.next {
  border-radius: 0 50% 50% 0;
  top: 50%;
  right: 10%;
}