This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
imagerepo/app/templates/gallery.html
T
2026-02-05 21:23:59 -05:00

270 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- /app/templates/gallery.html -->
{% extends "layout.html" %}
{% block title %}Gallery{% endblock %}
{% block content %}
<!-- Floating Select Button (in navbar area) -->
<div id="floatingSelectBtn" class="floating-select-btn">
<button id="selectModeBtn" class="btn secondary-btn">Select</button>
</div>
<div class="gallery-infinite-container">
<!-- Timeline Scrollbar (left sidebar) -->
<aside id="timelineSidebar" class="timeline-sidebar">
<div id="timelineTrack" class="timeline-track">
<!-- Populated by JS with year-month markers -->
<div class="timeline-loading">Loading...</div>
</div>
</aside>
<!-- Main Gallery Content -->
<main class="gallery-main">
<div class="gallery-header">
<h1>Gallery</h1>
{% if active_tag %}
<div class="active-filter">
Filtered by: <span class="filter-tag">{{ active_tag }}</span>
<a href="{{ url_for('main.gallery') }}" class="clear-filter" title="Clear filter">×</a>
</div>
{% endif %}
</div>
{% if post_metadata %}
<!-- Post Info Header -->
<div class="post-info-header">
<div class="post-info-content">
{% if post_metadata.title %}
<h2 class="post-title">{{ post_metadata.title }}</h2>
{% endif %}
<div class="post-meta">
{% if post_metadata.artist %}
<span class="post-artist">{{ post_metadata.artist }}</span>
{% endif %}
{% if post_metadata.platform %}
<span class="post-platform post-platform-{{ post_metadata.platform }}">{{ post_metadata.platform }}</span>
{% endif %}
{% if post_metadata.published_at %}
<span class="post-date">{{ post_metadata.published_at.strftime('%B %d, %Y') }}</span>
{% endif %}
{% if post_metadata.attachment_count > 1 %}
<span class="post-count">{{ post_metadata.attachment_count }} images</span>
{% endif %}
</div>
{% if post_metadata.description %}
<div class="post-description">
{{ post_metadata.description | safe }}
</div>
{% endif %}
{% if post_metadata.source_url %}
<a href="{{ post_metadata.source_url }}" target="_blank" rel="noopener noreferrer" class="post-source-link">
View original post
</a>
{% endif %}
</div>
</div>
{% endif %}
{% if series_info %}
<!-- Series Header -->
<div class="series-header">
<div class="series-header-content">
<div class="series-header-left">
<h2 class="series-title">{{ series_info.name }}</h2>
<span id="seriesPageCount" class="series-page-count">{{ series_info.page_count }} pages</span>
</div>
<div class="series-header-actions">
{% if series_info.page_count > 0 %}
<a href="{{ url_for('main.read_series', tag_id=series_info.tag_id) }}" class="btn primary-btn">
Read Series
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
<!-- Gallery Grid with Date Sections -->
<div id="galleryInfinite" class="gallery-infinite">
{% for year_month_key, year_month_label, images in grouped_images %}
<section class="date-section" data-year-month="{{ year_month_key }}">
<h2 class="date-divider" id="section-{{ year_month_key }}">
<span class="date-divider-text">{{ year_month_label }}</span>
<span class="date-divider-count">{{ images|length }} images</span>
</h2>
<div class="gallery-grid">
{% for image in images %}
{% include '_gallery_item.html' %}
{% endfor %}
</div>
</section>
{% else %}
<div class="gallery-empty">
<p>No images found{% if active_tag %} for tag "{{ active_tag }}"{% endif %}.</p>
</div>
{% endfor %}
</div>
<!-- Loading indicator -->
<div id="galleryLoading" class="gallery-loading" style="display:none;">
<span class="loading-spinner"></span>
<span>Loading more images...</span>
</div>
<!-- End of content marker -->
<div id="galleryEnd" class="gallery-end" style="display:none;">
No more images
</div>
</main>
</div>
<!-- Modal -->
{% include '_gallery_modal.html' %}
<!-- Bulk Editor Panel -->
<aside id="bulkEditorPanel" class="bulk-editor-panel">
<div class="bulk-editor-header">
<h3>Bulk Edit</h3>
<button id="closeBulkEditor" class="modal-close-btn">×</button>
</div>
<div class="bulk-editor-stats">
<span id="selectedCount">0</span> images selected
</div>
<div class="bulk-editor-section">
<h4>Add Tags</h4>
<form id="bulkAddTagForm" class="tag-form" autocomplete="off">
<div class="tag-form-wrapper">
<input type="text" id="bulkTagInput" name="name" placeholder="Add tag..." autocomplete="off">
<div id="bulkTagAutocomplete" class="tag-autocomplete"></div>
</div>
<button type="submit">Add</button>
</form>
</div>
<div class="bulk-editor-section">
<h4>Remove Tags</h4>
<p class="form-help">Common tags across selected images:</p>
<div id="commonTagsList" class="tags"></div>
</div>
{% if series_info %}
<div class="bulk-editor-section bulk-editor-series">
<h4>📚 Add to Series</h4>
<p class="form-help">Add selected images as pages to "{{ series_info.name }}"</p>
<div class="series-editor-controls">
<div class="series-editor-input">
<label for="startPageInput">Starting page:</label>
<input type="number" id="startPageInput" value="{{ series_info.page_count + 1 }}" min="1" class="form-input">
</div>
<button id="bulkAddToSeriesBtn" class="btn primary-btn" disabled>
Add to Series
</button>
</div>
<div id="seriesEditorFeedback" class="series-editor-feedback"></div>
</div>
{% endif %}
<div class="bulk-editor-actions">
<button id="clearSelectionBtn" class="btn secondary-btn">Clear Selection</button>
</div>
</aside>
<!-- Pass initial state to JS -->
<script>
window.galleryState = {
cursor: {{ initial_cursor | tojson | safe if initial_cursor else 'null' }},
hasMore: {{ 'true' if has_more else 'false' }},
activeTag: {{ active_tag | tojson | safe if active_tag else 'null' }},
seriesInfo: {{ series_info | tojson | safe if series_info else 'null' }}
};
</script>
<script src="{{ url_for('static', filename='js/tag-utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/gallery-infinite.js') }}"></script>
<script src="{{ url_for('static', filename='js/view-modal.js') }}"></script>
<script src="{{ url_for('static', filename='js/bulk-select.js') }}"></script>
{% if series_info %}
<script>
// Series Editor functionality (integrated into bulk editor panel)
document.addEventListener('DOMContentLoaded', () => {
const startPageInput = document.getElementById('startPageInput');
const bulkAddToSeriesBtn = document.getElementById('bulkAddToSeriesBtn');
const feedbackEl = document.getElementById('seriesEditorFeedback');
const seriesTagId = {{ series_info.tag_id }};
// Update button state based on selection
function updateAddButtonState() {
const selectedCount = window.selectedImages ? window.selectedImages.size : 0;
bulkAddToSeriesBtn.disabled = selectedCount === 0;
bulkAddToSeriesBtn.textContent = selectedCount > 0
? `Add ${selectedCount} to Series`
: 'Add to Series';
}
// Listen for selection changes
document.addEventListener('selectionChanged', updateAddButtonState);
// Add selected images to series
bulkAddToSeriesBtn.addEventListener('click', async () => {
if (!window.selectedImages || window.selectedImages.size === 0) return;
// Use selectionOrder (click order) for page ordering
const imageIds = window.selectionOrder ? [...window.selectionOrder] : Array.from(window.selectedImages);
const startPage = parseInt(startPageInput.value) || 1;
bulkAddToSeriesBtn.disabled = true;
bulkAddToSeriesBtn.textContent = 'Adding...';
feedbackEl.innerHTML = '';
try {
const r = await fetch(`/api/series/${seriesTagId}/pages/bulk-add`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image_ids: imageIds, start_page: startPage })
});
const data = await r.json();
if (data.ok) {
let msg = `Added ${data.added.length} image(s) to series.`;
if (data.skipped.length > 0) {
msg += ` Skipped ${data.skipped.length}.`;
}
feedbackEl.innerHTML = `<div class="feedback-success">${msg}</div>`;
// Update the starting page input to the next available page
if (data.added.length > 0) {
const lastPage = data.added[data.added.length - 1].page_number;
startPageInput.value = lastPage + 1;
}
// Clear selection
if (window.clearSelection) {
window.clearSelection();
}
// Update page count in header
const pageCountEl = document.getElementById('seriesPageCount');
if (pageCountEl) {
const currentCount = parseInt(pageCountEl.textContent) || 0;
pageCountEl.textContent = `${currentCount + data.added.length} pages`;
}
// Show "Read Series" button if it was hidden (first pages added)
const readBtn = document.querySelector('.series-header-actions .primary-btn');
if (!readBtn && data.added.length > 0) {
location.reload(); // Simplest way to update the UI
}
} else {
feedbackEl.innerHTML = `<div class="feedback-error">${data.error}</div>`;
}
} catch (e) {
feedbackEl.innerHTML = `<div class="feedback-error">Error: ${e.message}</div>`;
} finally {
updateAddButtonState();
}
});
// Initial state
updateAddButtonState();
});
</script>
{% endif %}
{% endblock %}