page load optimization and pagination changes
This commit is contained in:
+9
-7
@@ -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():
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
// /app/static/js/modal-pagination.js
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modal = document.getElementById('imageModal');
|
||||
const modalImage = document.getElementById('modalImage');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* /app/static/style.css */
|
||||
/* ========== General Layout ========== */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
@@ -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' %}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user