rework front page to have shuffle and lazy infinite scroll

This commit is contained in:
xenofem 2024-01-25 04:10:17 -05:00
parent 9729f45842
commit b75bad995a
8 changed files with 214 additions and 6 deletions

View file

@ -315,6 +315,7 @@ def generate(args):
list_template = jenv.get_template("list.html") list_template = jenv.get_template("list.html")
categorization_template = jenv.get_template("categorization.html") categorization_template = jenv.get_template("categorization.html")
work_template = jenv.get_template("work.html") work_template = jenv.get_template("work.html")
index_template = jenv.get_template("index.html")
con = sqlite3.connect(args.destdir / 'meta.db') con = sqlite3.connect(args.destdir / 'meta.db')
cur = con.cursor() cur = con.cursor()
@ -408,7 +409,7 @@ def generate(args):
copy_recursive(r / 'static', args.destdir / 'site' / 'static') copy_recursive(r / 'static', args.destdir / 'site' / 'static')
with open(args.destdir / 'site' / 'index.html', 'w') as f: 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() con.close()

View file

@ -5,9 +5,56 @@ body {
font-size: 18px; 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 */ /* listing stuff */
#title, nav { #title, .nav {
text-align: center; text-align: center;
} }
@ -20,7 +67,7 @@ body {
margin-bottom: 25px; margin-bottom: 25px;
} }
.card-listing { #card-listing {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;

View file

@ -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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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>

After

Width:  |  Height:  |  Size: 765 B

117
dlibrary/static/index.js Normal file
View file

@ -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();
};
});

View file

@ -4,7 +4,7 @@
{% from 'utils.html' import urlcat, index, root with context %} {% from 'utils.html' import urlcat, index, root with context %}
<h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a> &gt; {{ categorization.capitalize() }}</h1> <h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a> &gt; {{ categorization.capitalize() }}</h1>
{% include 'nav.html' %} {% include 'nav.html' %}
<div class="card-listing"> <div id="card-listing">
{% for cat in categories %} {% for cat in categories %}
<div class="card {% if not work_style_cards %}category{% endif %}"> <div class="card {% if not work_style_cards %}category{% endif %}">
<a href="{{ urlcat(cat) }}/{{ index() }}"> <a href="{{ urlcat(cat) }}/{{ index() }}">

View file

@ -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 %}

View file

@ -3,12 +3,12 @@
{% from 'utils.html' import index, root with context %} {% from 'utils.html' import index, root with context %}
<h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a>{% if categorization %} &gt; <a href="{{ root() }}/{{ categorization }}/{{ index() }}">{{ categorization.capitalize() }}</a>{% endif %}{% if title %} &gt; {{ title }}{% endif %}</h1> <h1 id="title"><a href="{{ root() }}/{{ index() }}">DLibrary</a>{% if categorization %} &gt; <a href="{{ root() }}/{{ categorization }}/{{ index() }}">{{ categorization.capitalize() }}</a>{% endif %}{% if title %} &gt; {{ title }}{% endif %}</h1>
{% include 'nav.html' %} {% include 'nav.html' %}
<div class="card-listing"> <div id="card-listing">
{% for work in works %} {% for work in works %}
<div class="card"> <div class="card">
<a href="{{ root() }}/works/{{ work['id'] }}/{{ index() }}"> <a href="{{ root() }}/works/{{ work['id'] }}/{{ index() }}">
<img src="{{ root() }}/thumbnails/{{ work['id'] }}.jpg"> <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 %}] [{% 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>
<div class="card-title"> <div class="card-title">