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)
|
return render_template('index.html', background_url=background_url)
|
||||||
|
|
||||||
|
|
||||||
@main.route('/gallery')
|
@main.route('/gallery')
|
||||||
@login_required
|
@login_required
|
||||||
def gallery():
|
def gallery():
|
||||||
per_page = 35
|
|
||||||
page = request.args.get('page', 1, type=int)
|
page = request.args.get('page', 1, type=int)
|
||||||
|
per_page = request.args.get('per_page', 35, type=int)
|
||||||
|
|
||||||
|
|
||||||
images = ImageRecord.query.order_by(
|
images = ImageRecord.query.order_by(
|
||||||
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
|
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
|
||||||
@@ -41,9 +41,9 @@ def gallery():
|
|||||||
'gallery.html',
|
'gallery.html',
|
||||||
images=images,
|
images=images,
|
||||||
has_next=images.has_next,
|
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,
|
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>')
|
@main.route('/artist/<artist_name>')
|
||||||
@login_required
|
@login_required
|
||||||
def artist_gallery(artist_name):
|
def artist_gallery(artist_name):
|
||||||
per_page = 35
|
|
||||||
page = request.args.get('page', 1, type=int)
|
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(
|
images = ImageRecord.query.filter_by(artist=artist_name).order_by(
|
||||||
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
|
desc(func.coalesce(ImageRecord.taken_at, ImageRecord.imported_at))
|
||||||
).paginate(page=page, per_page=per_page)
|
).paginate(page=page, per_page=per_page)
|
||||||
@@ -65,11 +66,12 @@ def artist_gallery(artist_name):
|
|||||||
images=images,
|
images=images,
|
||||||
artist=artist_name,
|
artist=artist_name,
|
||||||
has_next=images.has_next,
|
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,
|
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')
|
@main.route('/artists')
|
||||||
@login_required
|
@login_required
|
||||||
def artist_list():
|
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', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const modal = document.getElementById('imageModal');
|
const modal = document.getElementById('imageModal');
|
||||||
const modalImage = document.getElementById('modalImage');
|
const modalImage = document.getElementById('modalImage');
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* /app/static/style.css */
|
||||||
/* ========== General Layout ========== */
|
/* ========== General Layout ========== */
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
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 %}
|
{% if images.pages > 1 %}
|
||||||
<div class="pagination-container">
|
<div class="pagination-container">
|
||||||
{% set total_pages = images.pages %}
|
{% set total_pages = images.pages %}
|
||||||
@@ -5,6 +6,15 @@
|
|||||||
{% set window_size = 7 %}
|
{% set window_size = 7 %}
|
||||||
{% set endpoint = request.endpoint %}
|
{% 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 %}
|
{% if total_pages <= window_size %}
|
||||||
{% set start_page = 1 %}
|
{% set start_page = 1 %}
|
||||||
{% set end_page = total_pages %}
|
{% set end_page = total_pages %}
|
||||||
@@ -19,17 +29,9 @@
|
|||||||
{% set end_page = current_page + 3 %}
|
{% set end_page = current_page + 3 %}
|
||||||
{% endif %}
|
{% 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 #}
|
{# Left ellipsis #}
|
||||||
{% if start_page > 1 %}
|
{% 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 %}
|
{% if start_page > 2 %}
|
||||||
<span class="pagination-ellipsis">…</span>
|
<span class="pagination-ellipsis">…</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
|
|
||||||
{# Page numbers #}
|
{# Page numbers #}
|
||||||
{% for p in range(start_page, end_page + 1) %}
|
{% 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 %}
|
{% endfor %}
|
||||||
|
|
||||||
{# Right ellipsis #}
|
{# Right ellipsis #}
|
||||||
@@ -45,14 +47,14 @@
|
|||||||
{% if end_page < total_pages - 1 %}
|
{% if end_page < total_pages - 1 %}
|
||||||
<span class="pagination-ellipsis">…</span>
|
<span class="pagination-ellipsis">…</span>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|
||||||
{# Next and Last #}
|
{# Next and Last #}
|
||||||
{% if images.has_next %}
|
{% 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 %}
|
{% 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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- /app/templates/artist_gallery.html -->
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block title %}{{ artist }}'s Gallery{% endblock %}
|
{% block title %}{{ artist }}'s Gallery{% endblock %}
|
||||||
|
|
||||||
@@ -7,21 +8,8 @@
|
|||||||
<!-- Pagination Controls -->
|
<!-- Pagination Controls -->
|
||||||
{% include '_pagination.html' %}
|
{% include '_pagination.html' %}
|
||||||
|
|
||||||
<div class="gallery-grid">
|
<!-- Gallery Grid -->
|
||||||
{% for image in images %}
|
{% include '_gallery_grid.html' %}
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Pagination Controls -->
|
<!-- Pagination Controls -->
|
||||||
{% include '_pagination.html' %}
|
{% include '_pagination.html' %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- /app/templates/gallery.html -->
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block title %}Gallery{% endblock %}
|
{% block title %}Gallery{% endblock %}
|
||||||
|
|
||||||
@@ -8,21 +9,7 @@
|
|||||||
{% include '_pagination.html' %}
|
{% include '_pagination.html' %}
|
||||||
|
|
||||||
<!-- Gallery Grid -->
|
<!-- Gallery Grid -->
|
||||||
<div class="gallery-grid">
|
{% include '_gallery_grid.html' %}
|
||||||
{% 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>
|
|
||||||
|
|
||||||
<!-- Pagination Controls (Bottom) -->
|
<!-- Pagination Controls (Bottom) -->
|
||||||
{% include '_pagination.html' %}
|
{% include '_pagination.html' %}
|
||||||
|
|||||||
@@ -56,23 +56,5 @@
|
|||||||
<input type="hidden" id="prevPageUrl" value="{{ prev_page_url }}">
|
<input type="hidden" id="prevPageUrl" value="{{ prev_page_url }}">
|
||||||
{% endif %}
|
{% 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+21
-16
@@ -11,12 +11,10 @@ import uuid
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import ImageRecord
|
from app.models import ImageRecord
|
||||||
|
|
||||||
THUMB_DIR = "/images/thumbs"
|
|
||||||
THUMB_SIZE = (400, 400)
|
THUMB_SIZE = (400, 400)
|
||||||
|
|
||||||
def import_images_task(source_dir, dest_dir):
|
def import_images_task(source_dir, dest_dir):
|
||||||
imported = []
|
imported = []
|
||||||
os.makedirs(THUMB_DIR, exist_ok=True)
|
|
||||||
|
|
||||||
for artist_dir in os.listdir(source_dir):
|
for artist_dir in os.listdir(source_dir):
|
||||||
artist_path = os.path.join(source_dir, artist_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)
|
dest_path = os.path.join(target_dir, filename)
|
||||||
shutil.copy2(full_path, dest_path)
|
shutil.copy2(full_path, dest_path)
|
||||||
|
|
||||||
# Generate thumbnail
|
# Generate thumbnail AFTER copying to /images
|
||||||
thumb_filename = f"{uuid.uuid4().hex}.jpg"
|
|
||||||
thumb_path = os.path.join(THUMB_DIR, thumb_filename)
|
|
||||||
try:
|
try:
|
||||||
generate_thumbnail(full_path, thumb_path)
|
thumb_path = generate_thumbnail(dest_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WARN] Failed to generate thumbnail: {e}")
|
print(f"[WARN] Failed to generate thumbnail: {e}")
|
||||||
thumb_path = None
|
thumb_path = None
|
||||||
@@ -75,26 +71,35 @@ def import_images_task(source_dir, dest_dir):
|
|||||||
db.session.add(record)
|
db.session.add(record)
|
||||||
imported.append(filename)
|
imported.append(filename)
|
||||||
|
|
||||||
# Add delay between each image
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
return f"Imported {len(imported)} images."
|
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):
|
def generate_thumbnail(image_path, size=(400, 400), overwrite=False):
|
||||||
return thumb_path
|
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:
|
with Image.open(image_path) as img:
|
||||||
img.thumbnail(size)
|
img.thumbnail(size)
|
||||||
img.save(thumb_path)
|
img.save(thumb_path)
|
||||||
|
|
||||||
return thumb_path
|
return str(thumb_path)
|
||||||
|
|
||||||
|
|
||||||
def calculate_hash(file_path):
|
def calculate_hash(file_path):
|
||||||
hash_func = hashlib.sha256()
|
hash_func = hashlib.sha256()
|
||||||
@@ -155,4 +160,4 @@ def extract_metadata(file_path):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WARN] Failed to read EXIF data: {file_path} – {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}")
|
print(f"[Thumbnail] Regenerated for {img.filename}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Thumbnail] Failed for {img.filename}: {e}")
|
print(f"[Thumbnail] Failed for {img.filename}: {e}")
|
||||||
|
time.sleep(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Image Importer] Error during thumbnail regeneration: {e}")
|
print(f"[Image Importer] Error during thumbnail regeneration: {e}")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user