page load optimization and pagination changes

This commit is contained in:
Bryan Van Deusen
2025-08-03 11:40:32 -04:00
parent 63918363c1
commit 0dc944ef25
11 changed files with 115 additions and 84 deletions
+9 -7
View File
@@ -26,12 +26,12 @@ def index():
return render_template('index.html', background_url=background_url)
@main.route('/gallery')
@login_required
def gallery():
per_page = 35
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 35, type=int)
images = ImageRecord.query.order_by(
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
@@ -41,9 +41,9 @@ def gallery():
'gallery.html',
images=images,
has_next=images.has_next,
next_page_url=url_for('main.gallery', page=images.next_num) if images.has_next else '',
next_page_url=url_for('main.gallery', page=images.next_num, per_page=per_page) if images.has_next else '',
has_prev=images.has_prev,
prev_page_url=url_for('main.gallery', page=images.prev_num) if images.has_prev else ''
prev_page_url=url_for('main.gallery', page=images.prev_num, per_page=per_page) if images.has_prev else ''
)
@@ -54,8 +54,9 @@ def serve_image(filename):
@main.route('/artist/<artist_name>')
@login_required
def artist_gallery(artist_name):
per_page = 35
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 35, type=int)
images = ImageRecord.query.filter_by(artist=artist_name).order_by(
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
).paginate(page=page, per_page=per_page)
@@ -65,11 +66,12 @@ def artist_gallery(artist_name):
images=images,
artist=artist_name,
has_next=images.has_next,
next_page_url=url_for('main.artist_gallery', artist_name=artist_name, page=images.next_num) if images.has_next else '',
next_page_url=url_for('main.artist_gallery', artist_name=artist_name, page=images.next_num, per_page=per_page) if images.has_next else '',
has_prev=images.has_prev,
prev_page_url=url_for('main.artist_gallery', artist_name=artist_name, page=images.prev_num) if images.has_prev else ''
prev_page_url=url_for('main.artist_gallery', artist_name=artist_name, page=images.prev_num, per_page=per_page) if images.has_prev else ''
)
@main.route('/artists')
@login_required
def artist_list():
+26
View File
@@ -0,0 +1,26 @@
document.addEventListener("DOMContentLoaded", function () {
const url = new URL(window.location.href);
const page = url.searchParams.get("page") || "1";
const perPage = url.searchParams.get("per_page");
if (page !== "1" || perPage) return;
const grid = document.querySelector(".gallery-grid");
if (!grid) return;
const sample = grid.querySelector(".gallery-item");
if (!sample) return;
const gridStyle = window.getComputedStyle(grid);
const gridGap = parseFloat(gridStyle.getPropertyValue("gap")) || 0;
const sampleWidth = sample.getBoundingClientRect().width + gridGap;
const containerWidth = grid.getBoundingClientRect().width;
const columns = Math.floor(containerWidth / sampleWidth);
const calculatedPerPage = columns * 5;
url.searchParams.set("per_page", calculatedPerPage);
url.searchParams.set("page", "1");
window.location.replace(url.toString());
});
+1
View File
@@ -1,3 +1,4 @@
// /app/static/js/modal-pagination.js
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('imageModal');
const modalImage = document.getElementById('modalImage');
+1
View File
@@ -1,3 +1,4 @@
/* /app/static/style.css */
/* ========== General Layout ========== */
body {
font-family: Arial, sans-serif;
+36
View File
@@ -0,0 +1,36 @@
<!-- /app/templates/_gallery_grid.html -->
<div class="gallery-grid">
{% for image in images.items %}
<div class="gallery-item">
<div class="gallery-thumb img-clickable"
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');"
data-filename="{{ image.filename }}">
</div>
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
<span class="image-date">{{ image.taken_at or image.imported_at | datetimeformat }}</span>
</div>
{% endfor %}
</div>
<!-- Modal -->
<div id="imageModal" class="modal">
<div class="modal-content">
<!-- Navigation buttons -->
<button id="modalPrev" class="modal-button modal-prev"></button>
<button id="modalNext" class="modal-button modal-next"></button>
<div class="modal-body">
<div id="modalImageWrapper" class="modal-image-wrapper">
<img id="modalImage" src="" alt="Full View">
</div>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/modal-pagination.js') }}"></script>
{% if request.args.get('page', '1') == '1' and not request.args.get('per_page') %}
<script src="{{ url_for('static', filename='js/dynamic_gallery_paging.js') }}"></script>
{% endif %}
+15 -13
View File
@@ -1,3 +1,4 @@
{% set per_page = request.args.get('per_page') %}
{% if images.pages > 1 %}
<div class="pagination-container">
{% set total_pages = images.pages %}
@@ -5,6 +6,15 @@
{% set window_size = 7 %}
{% set endpoint = request.endpoint %}
{# First and Prev #}
{% if images.has_prev %}
{% if current_page > 1 %}
<a class="pagination-link" href="{{ url_for(endpoint, page=1, per_page=per_page, **request.view_args) }}">« First</a>
{% endif %}
<a class="pagination-link" href="{{ url_for(endpoint, page=images.prev_num, per_page=per_page, **request.view_args) }}">← Previous</a>
{% endif %}
{# Page window logic #}
{% if total_pages <= window_size %}
{% set start_page = 1 %}
{% set end_page = total_pages %}
@@ -19,17 +29,9 @@
{% set end_page = current_page + 3 %}
{% endif %}
{# First and Prev #}
{% if images.has_prev %}
{% if current_page > 1 %}
<a class="pagination-link" href="{{ url_for(endpoint, page=1, **request.view_args) }}">« First</a>
{% endif %}
<a class="pagination-link" href="{{ url_for(endpoint, page=images.prev_num, **request.view_args) }}">← Previous</a>
{% endif %}
{# Left ellipsis #}
{% if start_page > 1 %}
<a class="pagination-link" href="{{ url_for(endpoint, page=1, **request.view_args) }}">1</a>
<a class="pagination-link" href="{{ url_for(endpoint, page=1, per_page=per_page, **request.view_args) }}">1</a>
{% if start_page > 2 %}
<span class="pagination-ellipsis"></span>
{% endif %}
@@ -37,7 +39,7 @@
{# Page numbers #}
{% for p in range(start_page, end_page + 1) %}
<a class="pagination-link {% if p == current_page %}active{% endif %}" href="{{ url_for(endpoint, page=p, **request.view_args) }}">{{ p }}</a>
<a class="pagination-link {% if p == current_page %}active{% endif %}" href="{{ url_for(endpoint, page=p, per_page=per_page, **request.view_args) }}">{{ p }}</a>
{% endfor %}
{# Right ellipsis #}
@@ -45,14 +47,14 @@
{% if end_page < total_pages - 1 %}
<span class="pagination-ellipsis"></span>
{% endif %}
<a class="pagination-link" href="{{ url_for(endpoint, page=total_pages, **request.view_args) }}">{{ total_pages }}</a>
<a class="pagination-link" href="{{ url_for(endpoint, page=total_pages, per_page=per_page, **request.view_args) }}">{{ total_pages }}</a>
{% endif %}
{# Next and Last #}
{% if images.has_next %}
<a class="pagination-link" href="{{ url_for(endpoint, page=images.next_num, **request.view_args) }}">Next →</a>
<a class="pagination-link" href="{{ url_for(endpoint, page=images.next_num, per_page=per_page, **request.view_args) }}">Next →</a>
{% if current_page < total_pages %}
<a class="pagination-link" href="{{ url_for(endpoint, page=total_pages, **request.view_args) }}">Last »</a>
<a class="pagination-link" href="{{ url_for(endpoint, page=total_pages, per_page=per_page, **request.view_args) }}">Last »</a>
{% endif %}
{% endif %}
</div>
+3 -15
View File
@@ -1,3 +1,4 @@
<!-- /app/templates/artist_gallery.html -->
{% extends "layout.html" %}
{% block title %}{{ artist }}'s Gallery{% endblock %}
@@ -7,21 +8,8 @@
<!-- Pagination Controls -->
{% include '_pagination.html' %}
<div class="gallery-grid">
{% for image in images %}
<div class="gallery-item">
<div class="gallery-thumb img-clickable"
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');"
data-filename="{{ image.filename }}">
</div>
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
<span class="image-date">
{{ image.taken_at or image.imported_at | datetimeformat }}
</span>
</div>
{% endfor %}
</div>
<!-- Gallery Grid -->
{% include '_gallery_grid.html' %}
<!-- Pagination Controls -->
{% include '_pagination.html' %}
+2 -15
View File
@@ -1,3 +1,4 @@
<!-- /app/templates/gallery.html -->
{% extends "layout.html" %}
{% block title %}Gallery{% endblock %}
@@ -8,21 +9,7 @@
{% include '_pagination.html' %}
<!-- Gallery Grid -->
<div class="gallery-grid">
{% for image in images.items %}
<div class="gallery-item">
<div class="gallery-thumb img-clickable"
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}"
style="background-image: url('{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}');"
data-filename="{{ image.filename }}">
</div>
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
<span class="image-date">
{{ image.taken_at or image.imported_at | datetimeformat }}
</span>
</div>
{% endfor %}
</div>
{% include '_gallery_grid.html' %}
<!-- Pagination Controls (Bottom) -->
{% include '_pagination.html' %}
-18
View File
@@ -56,23 +56,5 @@
<input type="hidden" id="prevPageUrl" value="{{ prev_page_url }}">
{% endif %}
<!-- Modal -->
<div id="imageModal" class="modal">
<div class="modal-content">
<!-- Navigation buttons -->
<button id="modalPrev" class="modal-button modal-prev"></button>
<button id="modalNext" class="modal-button modal-next"></button>
<div class="modal-body">
<div id="modalImageWrapper" class="modal-image-wrapper">
<img id="modalImage" src="" alt="Full View">
</div>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/modal-pagination.js') }}"></script>
</body>
</html>
+21 -16
View File
@@ -11,12 +11,10 @@ import uuid
from app import db
from app.models import ImageRecord
THUMB_DIR = "/images/thumbs"
THUMB_SIZE = (400, 400)
def import_images_task(source_dir, dest_dir):
imported = []
os.makedirs(THUMB_DIR, exist_ok=True)
for artist_dir in os.listdir(source_dir):
artist_path = os.path.join(source_dir, artist_dir)
@@ -48,11 +46,9 @@ def import_images_task(source_dir, dest_dir):
dest_path = os.path.join(target_dir, filename)
shutil.copy2(full_path, dest_path)
# Generate thumbnail
thumb_filename = f"{uuid.uuid4().hex}.jpg"
thumb_path = os.path.join(THUMB_DIR, thumb_filename)
# Generate thumbnail AFTER copying to /images
try:
generate_thumbnail(full_path, thumb_path)
thumb_path = generate_thumbnail(dest_path)
except Exception as e:
print(f"[WARN] Failed to generate thumbnail: {e}")
thumb_path = None
@@ -75,26 +71,35 @@ def import_images_task(source_dir, dest_dir):
db.session.add(record)
imported.append(filename)
# Add delay between each image
db.session.commit()
time.sleep(1)
return f"Imported {len(imported)} images."
def generate_thumbnail(image_path, size=(400, 400), overwrite=False):
thumbnail_dir = THUMB_DIR # update this accordingly
basename = os.path.basename(image_path)
thumb_path = os.path.join(thumbnail_dir, basename)
if not overwrite and os.path.exists(thumb_path):
return thumb_path
def generate_thumbnail(image_path, size=(400, 400), overwrite=False):
from pathlib import Path
images_root = Path("/images").resolve()
image_path = Path(image_path).resolve()
try:
rel_path = image_path.relative_to(images_root)
except ValueError:
raise ValueError(f"{image_path} is not under {images_root}")
thumb_path = images_root / "thumbs" / rel_path
thumb_path.parent.mkdir(parents=True, exist_ok=True)
if not overwrite and thumb_path.exists():
return str(thumb_path)
with Image.open(image_path) as img:
img.thumbnail(size)
img.save(thumb_path)
return thumb_path
return str(thumb_path)
def calculate_hash(file_path):
hash_func = hashlib.sha256()
@@ -155,4 +160,4 @@ def extract_metadata(file_path):
except Exception as e:
print(f"[WARN] Failed to read EXIF data: {file_path} {e}")
return metadata
return metadata
+1
View File
@@ -44,6 +44,7 @@ def monitor_and_import():
print(f"[Thumbnail] Regenerated for {img.filename}")
except Exception as e:
print(f"[Thumbnail] Failed for {img.filename}: {e}")
time.sleep(1)
except Exception as e:
print(f"[Image Importer] Error during thumbnail regeneration: {e}")
finally: