major updates to theming, creation of showcase view and polish of existing systems including tagging editting.

This commit is contained in:
Bryan Van Deusen
2026-01-18 11:32:21 -05:00
parent 5f568f43bc
commit 46144ccc76
21 changed files with 1787 additions and 557 deletions
+43 -35
View File
@@ -1,39 +1,44 @@
<!-- /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-id="{{ image.id }}"
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}"
data-type="{{ 'video' if image.filename.lower().endswith(('.mp4', '.mov')) else 'image' }}"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');"
data-filename="{{ image.filename }}">
<div class="gallery-item img-clickable"
data-id="{{ image.id }}"
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '') ) }}"
data-type="{{ 'video' if image.filename.lower().endswith(('.mp4', '.mov')) else 'image' }}"
data-filename="{{ image.filename }}">
{% if image.tags %}
<div class="tag-overlay">
{% for t in image.tags %}
<a class="tag-chip"
href="{{ url_for('main.gallery', tag=t.name) }}"
title="Filter by {{ t.name }}"
onclick="event.stopPropagation()">
{% if t.kind == 'artist' %}
🎨 {{ t.name.split(':', 1)[1] }}
{% elif t.kind == 'archive' %}
🗜️ {{ t.name.split(':', 1)[1] }}
{% else %}
#{{ t.name }}
{% endif %}
</a>
{% endfor %}
</div>
{% endif %}
<div class="gallery-thumb"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');"></div>
{% if image.filename.lower().endswith(('.mp4', '.mov')) %}
<div class="play-overlay"></div>
{% endif %}
</div>
{% if image.tags %}
<div class="tag-overlay">
{% for t in image.tags %}
<a class="tag-chip"
href="{{ url_for('main.gallery', tag=t.name) }}"
title="Filter by {{ t.name }}"
onclick="event.stopPropagation()">
{% if t.kind == 'artist' %}
🎨 {{ t.name.split(':', 1)[1] if ':' in t.name else t.name }}
{% elif t.kind == 'archive' %}
🗜️ {{ t.name.split(':', 1)[1] if ':' in t.name else t.name }}
{% elif t.kind == 'character' %}
👤 {{ t.name.split(':', 1)[1] if ':' in t.name else t.name }}
{% elif t.kind == 'series' %}
📺 {{ t.name.split(':', 1)[1] if ':' in t.name else t.name }}
{% elif t.kind == 'rating' %}
⚠️ {{ t.name.split(':', 1)[1] if ':' in t.name else t.name }}
{% else %}
#{{ t.name }}
{% endif %}
</a>
{% endfor %}
</div>
{% endif %}
{% if image.filename.lower().endswith(('.mp4', '.mov')) %}
<div class="play-overlay"></div>
{% endif %}
<!-- Removed the artist link; keep date only -->
<span class="image-date">{{ image.taken_at or image.imported_at | datetimeformat }}</span>
</div>
{% endfor %}
@@ -44,20 +49,23 @@
<!-- 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>
<button id="modalClose" class="modal-close-btn" title="Close (ESC)">×</button>
<button id="modalPrev" class="modal-button modal-prev"></button>
<button id="modalNext" class="modal-button modal-next"></button>
<span class="modal-close-hint">ESC to close</span>
<div class="modal-body">
<div id="modalImageWrapper" class="modal-image-wrapper">
<img id="modalImage" src="" alt="Full View">
</div>
<!-- Tag editor (inline, minimal UI) -->
<div id="modalTagEditor" class="tag-editor" data-image-id="">
<div id="modalTagList" class="tags"></div>
<form id="modalTagForm" class="tag-form" autocomplete="off">
<input type="text" name="name" placeholder="Add tag… (artist:foo, archive:bar, or freeform)">
<div class="tag-form-wrapper">
<input type="text" id="tagInput" name="name" placeholder="Add tag..." autocomplete="off">
<div id="tagAutocomplete" class="tag-autocomplete"></div>
</div>
<button type="submit">Add</button>
</form>
</div>
+37
View File
@@ -0,0 +1,37 @@
<!-- /app/templates/_pagination_floating.html -->
{% set total_pages = images.pages %}
{% set current_page = images.page %}
{% set endpoint = request.endpoint %}
{# Build a merged param dict: query args + path args #}
{% set params = request.args.to_dict(flat=True) %}
{% for k, v in request.view_args.items() %}
{% set _ = params.update({k: v}) %}
{% endfor %}
{# Helper to make a page URL while preserving all other params #}
{% macro page_url(p) -%}
{%- set pmap = params.copy() -%}
{%- set _ = pmap.update({'page': p}) -%}
{{ url_for(endpoint, **pmap) }}
{%- endmacro %}
<div class="floating-pagination">
<a class="fp-btn fp-prev {% if not images.has_prev %}disabled{% endif %}"
{% if images.has_prev %}href="{{ page_url(images.prev_num) }}"{% endif %}
title="Previous page">
</a>
<span class="fp-indicator">
<span class="fp-current">{{ current_page }}</span>
<span class="fp-sep">/</span>
<span class="fp-total">{{ total_pages }}</span>
</span>
<a class="fp-btn fp-next {% if not images.has_next %}disabled{% endif %}"
{% if images.has_next %}href="{{ page_url(images.next_num) }}"{% endif %}
title="Next page">
</a>
</div>
-17
View File
@@ -1,17 +0,0 @@
<!-- /app/templates/artist_gallery.html -->
{% extends "layout.html" %}
{% block title %}{{ artist }}'s Gallery{% endblock %}
{% block content %}
<h1 style="text-align:center; margin-top: 1rem;">{{ artist }}</h1>
<!-- Pagination Controls -->
{% include '_pagination.html' %}
<!-- Gallery Grid -->
{% include '_gallery_grid.html' %}
<!-- Pagination Controls -->
{% include '_pagination.html' %}
{% endblock %}
-22
View File
@@ -1,22 +0,0 @@
{% extends "layout.html" %}
{% block title %}Artists{% endblock %}
{% block content %}
<h1>Artists</h1>
<div class="artist-grid">
{% for artist in artist_data %}
<a href="{{ url_for('main.artist_gallery', artist_name=artist.name) }}" class="artist-card-link">
<div class="artist-card">
<h3>{{ artist.name }}</h3>
<div class="artist-preview">
{% for image in artist.images %}
<div class="artist-thumb"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');">
</div>
{% endfor %}
</div>
</div>
</a>
{% endfor %}
</div>
{% endblock %}
+4 -5
View File
@@ -5,13 +5,12 @@
{% block content %}
<h1 style="text-align:center; margin-top: 1rem;">Gallery</h1>
<!-- Pagination Controls (Top) -->
{% include '_pagination.html' %}
<!-- Gallery Grid -->
{% include '_gallery_grid.html' %}
<!-- Pagination Controls (Bottom) -->
{% include '_pagination.html' %}
<!-- Floating Pagination Bar -->
{% if images.pages > 1 %}
{% include '_pagination_floating.html' %}
{% endif %}
{% endblock %}
-14
View File
@@ -1,14 +0,0 @@
{% extends "layout.html" %}
{% block title %}Welcome{% endblock %}
{% block content %}
<div class="index-hero" style="background-image: url('{{ background_url }}');">
<div class="index-overlay">
<div class="index-message">
<h1>Private Image Repository</h1>
<p>This is a private image collection. You must register for access to browse and contribute.</p>
<a href="{{ url_for('auth.register') }}" class="btn primary-btn">Register</a>
</div>
</div>
</div>
{% endblock %}
+3 -18
View File
@@ -11,24 +11,11 @@
<header>
<nav class="navbar">
<a class="nav-button" href="{{ url_for('main.gallery') }}">Home</a>
<a class="nav-button" href="{{ url_for('main.index') }}">Showcase</a>
<a class="nav-button" href="{{ url_for('main.gallery') }}">Gallery</a>
<a class="nav-button" href="{{ url_for('main.tag_list', kind='artist') }}">Artists</a>
<a class="nav-button" href="{{ url_for('main.tag_list') }}">Tags</a>
<a class="nav-button" href="{{ url_for('main.settings') }}">Settings</a>
<!-- <a class="nav-button" href="{{ url_for('main.gallery') if current_user.is_authenticated else url_for('main.index') }}">Home</a>
{% if current_user.is_authenticated and current_user.is_admin %}
<a class="nav-button" href="{{ url_for('main.settings') }}">Settings</a>
{% endif %}
{% if current_user.is_authenticated %}
<a class="nav-button" href="{{ url_for('main.tag_list', kind='artist') }}">Artists</a>
<a class="nav-button" href="{{ url_for('main.tag_list') }}">Tags</a>
<a class="nav-button" href="{{ url_for('auth.logout') }}">Logout</a>
{% else %}
<a class="nav-button" href="{{ url_for('auth.login') }}">Login</a>
<a class="nav-button" href="{{ url_for('auth.register') }}">Register</a>
{% endif %} -->
</nav>
</header>
@@ -44,15 +31,13 @@
{% endif %}
{% endwith %}
<hr>
<!-- MAIN CONTENT -->
<div class="content">
{% block content %}{% endblock %}
</div>
</div>
{% if request.endpoint == 'main.gallery' or request.endpoint == 'main.artist_gallery' %}
{% if request.endpoint == 'main.gallery' %}
<input type="hidden" id="hasNextPage" value="{{ has_next|lower }}">
<input type="hidden" id="nextPageUrl" value="{{ next_page_url }}">
<input type="hidden" id="hasPrevPage" value="{{ has_prev|lower }}">
-30
View File
@@ -1,30 +0,0 @@
{% extends "layout.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="form-container">
<h2>Login</h2>
<form method="POST" class="form-card">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-input", placeholder=form.email.label.text) }}
</div>
<div class="form-group">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-input", placeholder=form.password.label.text) }}
</div>
<div class="form-actions">
{{ form.submit(class="form-button") }}
</div>
</form>
<p class="form-footer-text">
Dont have an account? <a href="{{ url_for('auth.register') }}">Register here</a>.
</p>
</div>
{% endblock %}
-52
View File
@@ -1,52 +0,0 @@
{% extends "layout.html" %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="form-container">
<h2>Register</h2>
<form method="POST" class="form-card">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-input", placeholder=form.username.label.text) }}
{% for error in form.username.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-input", placeholder=form.email.label.text) }}
{% for error in form.email.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-input", placeholder=form.password.label.text) }}
{% for error in form.password.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-input", placeholder=form.confirm_password.label.text) }}
{% for error in form.confirm_password.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-actions">
{{ form.submit(class="form-button") }}
</div>
</form>
<p class="form-footer-text">
Already have an account? <a href="{{ url_for('auth.login') }}">Login here</a>.
</p>
</div>
{% endblock %}
+70
View File
@@ -0,0 +1,70 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ImageRepo - Showcase</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body class="showcase-body">
<!-- Floating navbar overlay -->
<nav class="showcase-nav">
<div class="showcase-nav-links">
<a class="nav-link" href="{{ url_for('main.index') }}">Showcase</a>
<a class="nav-link" href="{{ url_for('main.gallery') }}">Gallery</a>
<a class="nav-link" href="{{ url_for('main.tag_list', kind='artist') }}">Artists</a>
<a class="nav-link" href="{{ url_for('main.tag_list') }}">Tags</a>
<a class="nav-link" href="{{ url_for('main.settings') }}">Settings</a>
</div>
<span class="nav-hint">Press R to shuffle</span>
</nav>
<!-- Masonry grid container - columns created by JS -->
<div id="showcaseGrid" class="masonry-container"></div>
<!-- Initial image data for JS to distribute -->
<script id="initialImages" type="application/json">
[{% for image in images %}
{
"id": {{ image.id }},
"filename": "{{ image.filename | e }}",
"thumb_url": "{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '')) }}",
"full_url": "{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}",
"is_video": {{ 'true' if image.filename.lower().endswith(('.mp4', '.mov')) else 'false' }},
"tags": [{% for t in image.tags %}{"name": "{{ t.name | e }}", "kind": "{{ t.kind }}"}{% if not loop.last %}, {% endif %}{% endfor %}]
}{% if not loop.last %},{% endif %}
{% endfor %}]
</script>
<!-- Modal viewer -->
<div id="imageModal" class="modal">
<div class="modal-content">
<button id="modalClose" class="modal-close-btn" title="Close (ESC)">×</button>
<button id="modalPrev" class="modal-button modal-prev"></button>
<button id="modalNext" class="modal-button modal-next"></button>
<span class="modal-close-hint">ESC to close</span>
<div class="modal-body">
<div id="modalImageWrapper" class="modal-image-wrapper">
<img id="modalImage" src="" alt="Full View">
</div>
<div id="modalTagEditor" class="tag-editor" data-image-id="">
<div id="modalTagList" class="tags"></div>
<form id="modalTagForm" class="tag-form" autocomplete="off">
<div class="tag-form-wrapper">
<input type="text" id="tagInput" name="name" placeholder="Add tag..." autocomplete="off">
<div id="tagAutocomplete" class="tag-autocomplete"></div>
</div>
<button type="submit">Add</button>
</form>
</div>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/modal-pagination.js') }}"></script>
<script src="{{ url_for('static', filename='js/showcase.js') }}"></script>
</body>
</html>
+92 -16
View File
@@ -1,10 +1,25 @@
{% extends "layout.html" %}
{% block title %}{{ 'Artists' if active_kind == 'artist' else 'Tags' }}{% endblock %}
{% block title %}
{%- if active_kind == 'artist' -%}Artists
{%- elif active_kind == 'character' -%}Characters
{%- elif active_kind == 'series' -%}Series
{%- elif active_kind == 'rating' -%}Ratings
{%- elif active_kind == 'archive' -%}Archives
{%- elif active_kind == 'user' -%}User Tags
{%- else -%}Tags{%- endif -%}
{% endblock %}
{% block content %}
<h1>{{ 'Artists' if active_kind == 'artist' else 'Tags' }}</h1>
<h1>
{%- if active_kind == 'artist' -%}Artists
{%- elif active_kind == 'character' -%}Characters
{%- elif active_kind == 'series' -%}Series
{%- elif active_kind == 'rating' -%}Ratings
{%- elif active_kind == 'archive' -%}Archives
{%- elif active_kind == 'user' -%}User Tags
{%- else -%}All Tags{%- endif -%}
</h1>
<!-- Small kind filter nav (optional) -->
<!-- Tag kind filter -->
<div class="filter-bar" role="tablist" aria-label="Filter tags by kind">
<a class="filter-btn {{ 'is-active' if not active_kind }}"
@@ -15,6 +30,18 @@
href="{{ url_for('main.tag_list', kind='artist') }}"
aria-current="{{ 'page' if active_kind=='artist' else 'false' }}">🎨 Artists</a>
<a class="filter-btn {{ 'is-active' if active_kind=='character' }}"
href="{{ url_for('main.tag_list', kind='character') }}"
aria-current="{{ 'page' if active_kind=='character' else 'false' }}">👤 Characters</a>
<a class="filter-btn {{ 'is-active' if active_kind=='series' }}"
href="{{ url_for('main.tag_list', kind='series') }}"
aria-current="{{ 'page' if active_kind=='series' else 'false' }}">📺 Series</a>
<a class="filter-btn {{ 'is-active' if active_kind=='rating' }}"
href="{{ url_for('main.tag_list', kind='rating') }}"
aria-current="{{ 'page' if active_kind=='rating' else 'false' }}">⚠️ Ratings</a>
<a class="filter-btn {{ 'is-active' if active_kind=='archive' }}"
href="{{ url_for('main.tag_list', kind='archive') }}"
aria-current="{{ 'page' if active_kind=='archive' else 'false' }}">🗜️ Archives</a>
@@ -24,25 +51,74 @@
aria-current="{{ 'page' if active_kind=='user' else 'false' }}"># User</a>
</div>
<div class="artist-grid">
{% if tag_data %}
<div class="tag-grid">
{% for tag in tag_data %}
<a href="{{ url_for('main.gallery', tag=tag.tag) }}" class="artist-card-link">
<div class="artist-card">
<div class="card-title" title="{{ tag.name }}">
<h3 class="card-title-text">
{% if tag.kind == 'artist' %}🎨{% elif tag.kind == 'archive' %}🗜️{% else %}# {% endif %}
{{ tag.name }}
</h3>
</div>
<div class="artist-preview">
<div class="tag-card" data-tag-id="{{ tag.id }}" data-tag-name="{{ tag.tag }}" data-tag-kind="{{ tag.kind }}">
<a href="{{ url_for('main.gallery', tag=tag.tag) }}" class="tag-card-link">
<div class="tag-preview">
{% for image in tag.images %}
<div class="artist-thumb"
<div class="tag-thumb"
style="background-image: url('{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '') ) }}');">
</div>
{% endfor %}
{% if tag.images|length == 0 %}
<div class="tag-thumb tag-thumb-empty"></div>
{% endif %}
</div>
</div>
</a>
<div class="tag-card-info">
<span class="tag-icon">
{%- if tag.kind == 'artist' -%}🎨
{%- elif tag.kind == 'character' -%}👤
{%- elif tag.kind == 'series' -%}📺
{%- elif tag.kind == 'rating' -%}⚠️
{%- elif tag.kind == 'archive' -%}🗜️
{%- else -%}#{%- endif -%}
</span>
<span class="tag-name" title="{{ tag.name }}">{{ tag.name }}</span>
<span class="tag-count">{{ tag.count }}</span>
</div>
</a>
<button class="tag-edit-btn" title="Edit tag" data-tag-id="{{ tag.id }}">✏️</button>
</div>
{% endfor %}
</div>
{% else %}
<p class="empty-state">No tags found in this category.</p>
{% endif %}
<!-- Tag Edit Modal -->
<div id="tagEditModal" class="modal">
<div class="modal-content tag-edit-modal-content">
<button id="tagEditClose" class="modal-close-btn" title="Close">×</button>
<h2>Edit Tag</h2>
<form id="tagEditForm" class="tag-edit-form">
<input type="hidden" id="editTagId" name="tag_id">
<div class="form-group">
<label class="form-label" for="editTagName">Tag Name</label>
<input type="text" id="editTagName" name="name" class="form-input" required>
</div>
<div class="form-group">
<label class="form-label" for="editTagKind">Category</label>
<select id="editTagKind" name="kind" class="form-input">
<option value="user"># User Tag</option>
<option value="artist">🎨 Artist</option>
<option value="character">👤 Character</option>
<option value="series">📺 Series</option>
<option value="rating">⚠️ Rating</option>
<option value="archive">🗜️ Archive</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn primary-btn">Save Changes</button>
<button type="button" id="tagDeleteBtn" class="btn danger-btn">Delete Tag</button>
</div>
</form>
</div>
</div>
<script src="{{ url_for('static', filename='js/tag-editor.js') }}"></script>
{% endblock %}