diff --git a/.gitignore b/.gitignore
index 3b1f4ca..f88f74e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ prompts_log.jsonl
publish.sh
test.py
.vscode/launch.json
+favourites.json
diff --git a/routes/gallery_routes.py b/routes/gallery_routes.py
index 891c1d6..a8673b7 100644
--- a/routes/gallery_routes.py
+++ b/routes/gallery_routes.py
@@ -1,15 +1,58 @@
-from flask import Blueprint, render_template
+from flask import Blueprint, render_template, jsonify, request
import os
+import json
bp = Blueprint("gallery_routes", __name__)
image_folder = "./output"
+favourites_file = "./favourites.json"
+
+def get_favourites():
+ if not os.path.exists(favourites_file):
+ return []
+ with open(favourites_file, 'r') as f:
+ return json.load(f)
+
+def save_favourites(favourites):
+ with open(favourites_file, 'w') as f:
+ json.dump(favourites, f)
@bp.route("/images", methods=["GET"])
def gallery():
+ favourites = get_favourites()
images = [
- {"filename": f}
+ {"filename": f, "favourited": f in favourites}
for f in os.listdir(image_folder)
if f.lower().endswith(("png", "jpg", "jpeg", "gif"))
]
images = sorted(images, key=lambda x: os.path.getmtime(os.path.join(image_folder, x["filename"])), reverse=True)
return render_template("gallery.html", images=images)
+
+@bp.route("/favourites", methods=["GET"])
+def get_favourites_route():
+ return jsonify(get_favourites())
+
+@bp.route("/favourites/add", methods=["POST"])
+def add_favourite():
+ data = request.get_json()
+ filename = data.get("filename")
+ if not filename:
+ return jsonify({"status": "error", "message": "Filename missing"}), 400
+
+ favourites = get_favourites()
+ if filename not in favourites:
+ favourites.append(filename)
+ save_favourites(favourites)
+ return jsonify({"status": "success"})
+
+@bp.route("/favourites/remove", methods=["POST"])
+def remove_favourite():
+ data = request.get_json()
+ filename = data.get("filename")
+ if not filename:
+ return jsonify({"status": "error", "message": "Filename missing"}), 400
+
+ favourites = get_favourites()
+ if filename in favourites:
+ favourites.remove(filename)
+ save_favourites(favourites)
+ return jsonify({"status": "success"})
diff --git a/templates/gallery.html b/templates/gallery.html
index 680f0e6..145ffe3 100644
--- a/templates/gallery.html
+++ b/templates/gallery.html
@@ -116,6 +116,22 @@
/* lower than lightbox (999) */
}
+ .favourites-button {
+ position: fixed;
+ top: 20px;
+ right: 120px;
+ z-index: 500;
+ }
+
+ .favourite-heart {
+ position: absolute;
+ top: 20px;
+ left: 30px;
+ font-size: 30px;
+ color: white;
+ cursor: pointer;
+ }
+
.button-link {
background: #333;
color: white;
@@ -127,6 +143,7 @@
transition: background 0.3s;
display: inline-block;
text-align: center;
+ border: none;
}
.button-link:hover {
@@ -179,6 +196,7 @@
Home
+
Image Archive
@@ -188,6 +206,7 @@
×
+
♡
❮
@@ -196,10 +215,10 @@
@@ -209,12 +228,15 @@
let loadedCount = 0;
let currentIndex = 0;
const detailsCache = {}; // Cache for image details
+ let showingFavourites = false;
+ let filteredImages = allImages;
function createImageElement(image) {
const img = document.createElement('img');
img.src = `/images/thumbnails/${image.filename}`;
img.dataset.fullsrc = `/images/${image.filename}`;
img.dataset.filename = image.filename;
+ img.dataset.favourited = image.favourited;
img.loading = 'lazy';
img.style.cursor = 'pointer';
img.style.borderRadius = '10px';
@@ -223,7 +245,8 @@
}
function loadNextBatch() {
- const nextImages = allImages.slice(loadedCount, loadedCount + batchSize);
+ const imagesToLoad = showingFavourites ? filteredImages : allImages;
+ const nextImages = imagesToLoad.slice(loadedCount, loadedCount + batchSize);
nextImages.forEach(image => {
const imgEl = createImageElement(image);
gallery.appendChild(imgEl);
@@ -231,12 +254,32 @@
loadedCount += nextImages.length;
}
+ function renderGallery() {
+ gallery.innerHTML = '';
+ loadedCount = 0;
+ loadNextBatch();
+ }
+
+ function toggleFavouritesView() {
+ showingFavourites = !showingFavourites;
+ const button = document.getElementById('favourites-button');
+ if (showingFavourites) {
+ filteredImages = allImages.filter(img => img.favourited);
+ button.textContent = 'Show All';
+ } else {
+ filteredImages = allImages;
+ button.textContent = 'Show Favourites';
+ }
+ renderGallery();
+ }
+
// Load initial batch
- loadNextBatch();
+ renderGallery();
// Load more images when scrolling near bottom
window.addEventListener('scroll', () => {
- if (loadedCount >= allImages.length) return; // all loaded
+ const imagesToLoad = showingFavourites ? filteredImages : allImages;
+ if (loadedCount >= imagesToLoad.length) return; // all loaded
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 100)) {
loadNextBatch();
}
@@ -254,13 +297,48 @@
document.getElementById("lightbox").style.display = "flex";
}
+ function updateFavouriteHeart(isFavourited) {
+ const heart = document.getElementById('favourite-heart');
+ heart.innerHTML = isFavourited ? '♥' : '♡'; // solid vs outline heart
+ heart.style.color = isFavourited ? 'red' : 'white';
+ }
+
+ function toggleFavourite() {
+ const images = getGalleryImages();
+ const imgEl = images[currentIndex];
+ const filename = imgEl.dataset.filename;
+ let isFavourited = imgEl.dataset.favourited === 'true';
+
+ const url = isFavourited ? '/favourites/remove' : '/favourites/add';
+ fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ filename: filename })
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.status === 'success') {
+ isFavourited = !isFavourited;
+ imgEl.dataset.favourited = isFavourited;
+ updateFavouriteHeart(isFavourited);
+ // Update the main image list
+ const originalImage = allImages.find(img => img.filename === filename);
+ if (originalImage) {
+ originalImage.favourited = isFavourited;
+ }
+ }
+ });
+ }
+
function showImageAndLoadDetails(index) {
const images = getGalleryImages();
const imgEl = images[index];
const filename = imgEl.dataset.filename;
const fullsrc = imgEl.dataset.fullsrc;
+ const isFavourited = imgEl.dataset.favourited === 'true';
document.getElementById("lightbox-img").src = fullsrc;
+ updateFavouriteHeart(isFavourited);
if (detailsCache[filename]) {
document.getElementById("lightbox-prompt").textContent =
@@ -287,12 +365,16 @@
function nextImage() {
const images = getGalleryImages();
- if (currentIndex + 1 >= images.length && loadedCount < allImages.length) {
+ const imagesToLoad = showingFavourites ? filteredImages : allImages;
+ if (currentIndex + 1 >= images.length && loadedCount < imagesToLoad.length) {
loadNextBatch();
// Wait briefly to ensure DOM updates
setTimeout(() => {
- currentIndex++;
- showImageAndLoadDetails(currentIndex);
+ const updatedImages = getGalleryImages();
+ if (currentIndex + 1 < updatedImages.length) {
+ currentIndex++;
+ showImageAndLoadDetails(currentIndex);
+ }
}, 100);
} else {
currentIndex = (currentIndex + 1) % images.length;
@@ -309,6 +391,15 @@
function closeLightbox() {
document.getElementById("lightbox").style.display = "none";
+ if (showingFavourites) {
+ // Refresh the gallery if a favourite was removed
+ const currentImage = getGalleryImages()[currentIndex];
+ const wasFavourited = currentImage.dataset.favourited === 'true';
+ const originalImage = allImages.find(img => img.filename === currentImage.dataset.filename);
+ if (originalImage && !originalImage.favourited) {
+ renderGallery();
+ }
+ }
}