diff --git a/dlibrary/dlibrary.py b/dlibrary/dlibrary.py index 5598c1e..3d49ca0 100755 --- a/dlibrary/dlibrary.py +++ b/dlibrary/dlibrary.py @@ -315,6 +315,7 @@ def generate(args): list_template = jenv.get_template("list.html") categorization_template = jenv.get_template("categorization.html") work_template = jenv.get_template("work.html") + index_template = jenv.get_template("index.html") con = sqlite3.connect(args.destdir / 'meta.db') cur = con.cursor() @@ -408,7 +409,7 @@ def generate(args): copy_recursive(r / 'static', args.destdir / 'site' / 'static') with open(args.destdir / 'site' / 'index.html', 'w') as f: - f.write(list_template.render(depth=0, works=works)) + f.write(index_template.render(depth=0, works=works)) con.close() diff --git a/dlibrary/static/dlibrary.css b/dlibrary/static/dlibrary.css index f82feb5..aaa1b8e 100644 --- a/dlibrary/static/dlibrary.css +++ b/dlibrary/static/dlibrary.css @@ -5,9 +5,56 @@ body { font-size: 18px; } +/* index stuff */ + +#top { + display: flex; + justify-content: center; + align-items: end; + gap: 40px; + margin-bottom: 25px; +} + +#top .nav { + margin-bottom: 0px; +} + +#top-padding, #controls { + flex-grow: 1; + flex-basis: 0; +} + +@media all and (max-width: 600px) { + #top { + flex-direction: column; + align-items: center; + gap: 0; + } + #top .nav { + margin-bottom: 20px; + } +} + +#controls button { + position: relative; + width: 50px; + height: 50px; + margin: 5px 2px; +} + +#controls button img { + height: 40px; + width: 40px; + + position: absolute; + top: 50%; + left: 50%; + margin: -20px 0 0 -20px; +} + /* listing stuff */ -#title, nav { +#title, .nav { text-align: center; } @@ -20,7 +67,7 @@ body { margin-bottom: 25px; } -.card-listing { +#card-listing { display: flex; flex-wrap: wrap; justify-content: center; diff --git a/dlibrary/static/icons/shuffle.svg b/dlibrary/static/icons/shuffle.svg new file mode 100644 index 0000000..074e7f9 --- /dev/null +++ b/dlibrary/static/icons/shuffle.svg @@ -0,0 +1,12 @@ + diff --git a/dlibrary/static/icons/sort.svg b/dlibrary/static/icons/sort.svg new file mode 100644 index 0000000..a66a68d --- /dev/null +++ b/dlibrary/static/icons/sort.svg @@ -0,0 +1,6 @@ + diff --git a/dlibrary/static/index.js b/dlibrary/static/index.js new file mode 100644 index 0000000..409e537 --- /dev/null +++ b/dlibrary/static/index.js @@ -0,0 +1,117 @@ +const LCG_M = Math.pow(2, 32); +const LCG_A = 0xd9f5; +const LCG_C = 69; + +function lcg(seed) { + let value = seed % LCG_M; + + return (n) => { + value = (LCG_A * value + LCG_C) % LCG_M; + return Math.floor(n * value / LCG_M); + }; +} + +function seedableShuffledCopy(list, seed) { + const gen = lcg(seed); + const l = [...list]; + + for (let i = 0; i < l.length - 1; ++i) { + j = i + gen(l.length - i); + const tmp = l[i]; + l[i] = l[j]; + l[j] = tmp; + } + return l; +} + +function newSeed() { + return Math.floor(Math.random() * LCG_M); +} + +document.addEventListener('DOMContentLoaded', () => { + const shuffleButton = document.getElementById('shuffle'); + const sortButton = document.getElementById('sort'); + const listContainer = document.getElementById('card-listing'); + let ordering = localStorage.getItem('indexOrdering') || 'dateDesc'; + + let orderedWorks; + + function scrollHandler() { + while (orderedWorks.length > 0 && listContainer.clientHeight - window.scrollY < 5000) { + const work = orderedWorks.shift(); + + const card = document.createElement('div'); + card.className = 'card'; + + const link = document.createElement('a'); + link.href = `${ROOT}/works/${work.id}/${INDEX}`; + card.appendChild(link); + + const thumb = document.createElement('img'); + thumb.src = `${ROOT}/thumbnails/${work.id}.jpg`; + link.appendChild(thumb); + + const creators = document.createElement('div'); + creators.className = 'card-creators'; + let creatorsInfo = `[${work.circle || ''}`; + if (work.authors) { + let authorList = work.authors[0]; + for (let i = 1; i < work.authors.length; ++i) { + authorList += `, ${work.authors[i]}`; + } + creatorsInfo += (work.circle ? ` (${authorList})]` : `${authorList}]`); + } + creators.textContent = creatorsInfo; + link.appendChild(creators); + + const title = document.createElement('div'); + title.className = 'card-title'; + title.textContent = work.title; + link.appendChild(title); + + listContainer.appendChild(card); + } + } + + function applyOrdering() { + listContainer.replaceChildren(); + scrollHandler(); + } + + switch (ordering) { + case 'shuffle': + let seed = parseInt(localStorage.getItem('shuffleSeed')) || newSeed(); + orderedWorks = seedableShuffledCopy(WORKS, seed); + break; + case 'dateAsc': + orderedWorks = WORKS.toReversed(); + break; + default: + orderedWorks = [...WORKS]; + break; + } + applyOrdering(); + + window.addEventListener('scroll', scrollHandler); + + document.getElementById('shuffle').onclick = () => { + let seed = newSeed(); + localStorage.setItem('shuffleSeed', seed); + ordering = 'shuffle'; + localStorage.setItem('indexOrdering', ordering); + + orderedWorks = seedableShuffledCopy(WORKS, seed); + applyOrdering(); + }; + document.getElementById('sort').onclick = () => { + if (ordering === 'dateDesc') { + ordering = 'dateAsc'; + orderedWorks = WORKS.toReversed(); + } else { + ordering = 'dateDesc'; + orderedWorks = [...WORKS]; + } + localStorage.setItem('indexOrdering', ordering); + applyOrdering(); + }; +}); diff --git a/dlibrary/templates/categorization.html b/dlibrary/templates/categorization.html index 0039442..f903b2f 100644 --- a/dlibrary/templates/categorization.html +++ b/dlibrary/templates/categorization.html @@ -4,7 +4,7 @@ {% from 'utils.html' import urlcat, index, root with context %}