From b75bad995a58cd52717bd15bab2df6a2688e20fc Mon Sep 17 00:00:00 2001 From: xenofem <xenofem@xeno.science> Date: Thu, 25 Jan 2024 04:10:17 -0500 Subject: [PATCH 1/3] rework front page to have shuffle and lazy infinite scroll --- dlibrary/dlibrary.py | 3 +- dlibrary/static/dlibrary.css | 51 ++++++++++- dlibrary/static/icons/shuffle.svg | 12 +++ dlibrary/static/icons/sort.svg | 6 ++ dlibrary/static/index.js | 117 +++++++++++++++++++++++++ dlibrary/templates/categorization.html | 2 +- dlibrary/templates/index.html | 25 ++++++ dlibrary/templates/list.html | 4 +- 8 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 dlibrary/static/icons/shuffle.svg create mode 100644 dlibrary/static/icons/sort.svg create mode 100644 dlibrary/static/index.js create mode 100644 dlibrary/templates/index.html 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 @@ +<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='180' height='180' viewBox='-40 -40 180 180'> + <defs> + <clipPath id="crossing"> + <path d='M -30 40 L -10 40 C 65 40 25 140 100 140 L 120 140 L -30 140 Z'/> + <path d='M -20 -40 L -10 -40 C 65 -40 25 60 100 60 L 130 60 L 130 -40 Z'/> + </clipPath> + </defs> + <path d='M -20 0 L -10 0 C 65 0 25 100 100 100 L 120 100' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M -20 100 L -10 100 C 65 100 25 0 100 0 L 120 0' fill='none' clip-path='url(#crossing)' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M 105 85 L 120 100 L 105 115' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M 105 -15 L 120 0 L 105 15' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> +</svg> 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 @@ +<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='180' height='180' viewBox='-20 -20 180 180'> + <path d='M 30 0 L 30 140' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M 0 110 L 30 140 L 60 110' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M 110 0 L 110 140' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> + <path d='M 80 30 L 110 0 L 140 30' fill='none' stroke='#ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='10'/> +</svg> 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 %} <h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a> > {{ categorization.capitalize() }}</h1> {% include 'nav.html' %} -<div class="card-listing"> +<div id="card-listing"> {% for cat in categories %} <div class="card {% if not work_style_cards %}category{% endif %}"> <a href="{{ urlcat(cat) }}/{{ index() }}"> diff --git a/dlibrary/templates/index.html b/dlibrary/templates/index.html new file mode 100644 index 0000000..37aaa4f --- /dev/null +++ b/dlibrary/templates/index.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} +{% from 'utils.html' import index, root with context %} +{% block head %} +<script> + const ROOT = "{{ root() }}"; + const INDEX = "{{ index() }}"; + const WORKS = {{ works | tojson }}; +</script> +<script src="{{ root() }}/static/index.js"></script> +{% endblock %} +{% block body %} +<div id="top"> + <div id="top-padding"></div> + <div id="header"> + <h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a></h1> + {% include 'nav.html' %} + </div> + <div id="controls"> + <button id="shuffle" name="Shuffle"><img src="{{ root() }}/static/icons/shuffle.svg"/></button> + <button id="sort" name="Sort"><img src="{{ root() }}/static/icons/sort.svg"/></button> + </div> +</div> +<div id="card-listing"> +</div> +{% endblock %} diff --git a/dlibrary/templates/list.html b/dlibrary/templates/list.html index d0e1139..ec7165d 100644 --- a/dlibrary/templates/list.html +++ b/dlibrary/templates/list.html @@ -3,12 +3,12 @@ {% from 'utils.html' import index, root with context %} <h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a>{% if categorization %} > <a href="{{ root() }}/{{ categorization }}/{{ index() }}">{{ categorization.capitalize() }}</a>{% endif %}{% if title %} > {{ title }}{% endif %}</h1> {% include 'nav.html' %} -<div class="card-listing"> +<div id="card-listing"> {% for work in works %} <div class="card"> <a href="{{ root() }}/works/{{ work['id'] }}/{{ index() }}"> <img src="{{ root() }}/thumbnails/{{ work['id'] }}.jpg"> - <div class="card-authors"> + <div class="card-creators"> [{% if work['circle'] %}{{ work['circle'] }}{% endif %}{% if work['circle'] and work['authors'] %} ({% endif %}{{ ', '.join(work['authors']) }}{% if work['circle'] and work['authors'] %}){% endif %}] </div> <div class="card-title"> From 61049f0d11e19b8b0b47a95d69390e1b9725f3b4 Mon Sep 17 00:00:00 2001 From: xenofem <xenofem@xeno.science> Date: Thu, 25 Jan 2024 04:15:11 -0500 Subject: [PATCH 2/3] forgot empty arrays are truthy --- dlibrary/static/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlibrary/static/index.js b/dlibrary/static/index.js index 409e537..813b33a 100644 --- a/dlibrary/static/index.js +++ b/dlibrary/static/index.js @@ -54,7 +54,7 @@ document.addEventListener('DOMContentLoaded', () => { const creators = document.createElement('div'); creators.className = 'card-creators'; let creatorsInfo = `[${work.circle || ''}`; - if (work.authors) { + if (work.authors.length > 0) { let authorList = work.authors[0]; for (let i = 1; i < work.authors.length; ++i) { authorList += `, ${work.authors[i]}`; From cf23ca6bbe925a1e0628690b245395922e82f2ae Mon Sep 17 00:00:00 2001 From: xenofem <xenofem@xeno.science> Date: Thu, 25 Jan 2024 04:16:26 -0500 Subject: [PATCH 3/3] fix case with no authors --- dlibrary/static/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlibrary/static/index.js b/dlibrary/static/index.js index 813b33a..8d48595 100644 --- a/dlibrary/static/index.js +++ b/dlibrary/static/index.js @@ -59,8 +59,9 @@ document.addEventListener('DOMContentLoaded', () => { for (let i = 1; i < work.authors.length; ++i) { authorList += `, ${work.authors[i]}`; } - creatorsInfo += (work.circle ? ` (${authorList})]` : `${authorList}]`); + creatorsInfo += (work.circle ? ` (${authorList})` : `${authorList}`); } + creatorsInfo += ']'; creators.textContent = creatorsInfo; link.appendChild(creators);