mirror of
				https://github.com/karl0ss/ai_image_frame_server.git
				synced 2025-10-31 06:34:13 +00:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			73acc92ee4
			...
			7f78912829
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7f78912829 | |||
| 33404c7a37 | |||
| 0b7e0ca59d | |||
| eeb82a9a3a | |||
| 96deba17de | |||
| fe7a32b1de | 
| @ -1,5 +1,5 @@ | ||||
| [tool.bumpversion] | ||||
| current_version = "0.2.5" | ||||
| current_version = "0.2.8" | ||||
| parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)" | ||||
| serialize = ["{major}.{minor}.{patch}"] | ||||
| search = "{current_version}" | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -10,3 +10,4 @@ prompts_log.jsonl | ||||
| publish.sh | ||||
| test.py | ||||
| .vscode/launch.json | ||||
| favourites.json | ||||
|  | ||||
| @ -1,15 +1,50 @@ | ||||
| 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/toggle", methods=["POST"]) | ||||
| def toggle_favourite(): | ||||
|     data = request.get_json() | ||||
|     filename = data.get("filename") | ||||
|     if not filename: | ||||
|         return jsonify({"status": "error", "message": "Filename missing"}), 400 | ||||
|          | ||||
|     favourites = get_favourites() | ||||
|     is_favourited = False | ||||
|     if filename in favourites: | ||||
|         favourites.remove(filename) | ||||
|     else: | ||||
|         favourites.append(filename) | ||||
|         is_favourited = True | ||||
|          | ||||
|     save_favourites(favourites) | ||||
|     return jsonify({"status": "success", "favourited": is_favourited}) | ||||
|  | ||||
| @ -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,15 +196,17 @@ | ||||
| 
 | ||||
| <body> | ||||
|     <a href="/" class="button-link home-button">Home</a> | ||||
|     <button class="button-link favourites-button" id="favourites-button" onclick="toggleFavouritesView()">Show Favourites</button> | ||||
| 
 | ||||
|     <h1>Image Archive</h1> | ||||
|     <h1 id="page-title">Image Archive</h1> | ||||
| 
 | ||||
|     <!-- Empty gallery container; images will be loaded incrementally --> | ||||
|     <div class="gallery" id="gallery"></div> | ||||
| 
 | ||||
|     <!-- Lightbox --> | ||||
|     <div class="lightbox" id="lightbox"> | ||||
|     <div class="lightbox" id="lightbox" tabindex="-1" onkeyup="handleLightboxKeys(event)"> | ||||
|         <span class="close" onclick="closeLightbox()">×</span> | ||||
|         <span class="favourite-heart" id="favourite-heart" onclick="toggleFavourite()">♡</span> | ||||
|         <span class="arrow left" onclick="prevImage()">❮</span> | ||||
|         <img id="lightbox-img" src="" /> | ||||
|         <p id="lightbox-prompt"></p> | ||||
| @ -196,11 +215,11 @@ | ||||
| 
 | ||||
|     <!-- Pass image filenames from Flask to JS --> | ||||
|     <script> | ||||
|         const allImages = [ | ||||
|         let allImages = JSON.parse(`[ | ||||
|             {% for image in images %} | ||||
|         { filename: "{{ image.filename }}" }, | ||||
|         {% endfor %} | ||||
|         ]; | ||||
|                 { "filename": "{{ image.filename }}", "favourited": {{ 'true' if image.favourited else 'false' }} }{% if not loop.last %},{% endif %} | ||||
|             {% endfor %} | ||||
|         ]`); | ||||
|     </script> | ||||
| 
 | ||||
|     <script> | ||||
| @ -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,35 @@ | ||||
|             loadedCount += nextImages.length; | ||||
|         } | ||||
| 
 | ||||
|         function renderGallery() { | ||||
|             gallery.innerHTML = ''; | ||||
|             loadedCount = 0; | ||||
|             loadNextBatch(); | ||||
|         } | ||||
| 
 | ||||
|         function toggleFavouritesView() { | ||||
|             showingFavourites = !showingFavourites; | ||||
|             const button = document.getElementById('favourites-button'); | ||||
|             const pageTitle = document.getElementById('page-title'); | ||||
|             if (showingFavourites) { | ||||
|                 filteredImages = allImages.filter(img => img.favourited); | ||||
|                 button.textContent = 'Show All'; | ||||
|                 pageTitle.textContent = 'Favourites'; | ||||
|             } else { | ||||
|                 filteredImages = allImages; | ||||
|                 button.textContent = 'Show Favourites'; | ||||
|                 pageTitle.textContent = 'Image Archive'; | ||||
|             } | ||||
|             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(); | ||||
|             } | ||||
| @ -251,7 +297,45 @@ | ||||
|             const images = getGalleryImages(); | ||||
|             currentIndex = images.indexOf(imgEl); | ||||
|             showImageAndLoadDetails(currentIndex); | ||||
|             document.getElementById("lightbox").style.display = "flex"; | ||||
|             const lightbox = document.getElementById("lightbox"); | ||||
|             lightbox.style.display = "flex"; | ||||
|             lightbox.focus(); | ||||
|         } | ||||
| 
 | ||||
|         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; | ||||
| 
 | ||||
|             fetch('/favourites/toggle', { | ||||
|                 method: 'POST', | ||||
|                 headers: { 'Content-Type': 'application/json' }, | ||||
|                 body: JSON.stringify({ filename: filename }) | ||||
|             }) | ||||
|             .then(response => response.json()) | ||||
|             .then(data => { | ||||
|                 if (data.status === 'success') { | ||||
|                     const isFavourited = data.favourited; | ||||
|                     imgEl.dataset.favourited = isFavourited; | ||||
|                     updateFavouriteHeart(isFavourited); | ||||
|                      | ||||
|                     const originalImage = allImages.find(img => img.filename === filename); | ||||
|                     if (originalImage) { | ||||
|                         originalImage.favourited = isFavourited; | ||||
|                     } | ||||
|                      | ||||
|                     if (showingFavourites) { | ||||
|                         filteredImages = allImages.filter(img => img.favourited); | ||||
|                         renderGallery(); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         function showImageAndLoadDetails(index) { | ||||
| @ -259,8 +343,10 @@ | ||||
|             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 +373,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 +399,27 @@ | ||||
| 
 | ||||
|         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(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         function handleLightboxKeys(e) { | ||||
|             if (e.key === 'f') { | ||||
|                 toggleFavourite(); | ||||
|             } else if (e.key === 'Escape') { | ||||
|                 closeLightbox(); | ||||
|             } else if (e.key === 'ArrowLeft') { | ||||
|                 prevImage(); | ||||
|             } else if (e.key === 'ArrowRight') { | ||||
|                 nextImage(); | ||||
|             } | ||||
|         } | ||||
|     </script> | ||||
| </body> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user