dump fix, css fix, auto load, del cache.
							
								
								
									
										140
									
								
								app.py
								
								
								
								
							
							
						
						|  | @ -19,6 +19,7 @@ socketio = SocketIO(app) | ||||||
| app.config['UPLOAD_FOLDER'] = 'images'  # Folder to store images | app.config['UPLOAD_FOLDER'] = 'images'  # Folder to store images | ||||||
| app.config['DUMP_FOLDER'] = 'dump_images' | app.config['DUMP_FOLDER'] = 'dump_images' | ||||||
| app.config['CACHE_FOLDER'] = 'cache'  # Folder to store cached resized images | app.config['CACHE_FOLDER'] = 'cache'  # Folder to store cached resized images | ||||||
|  | app.config['DUMP_CACHE_FOLDER'] = 'dump_cache' | ||||||
| app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'} | app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'} | ||||||
| 
 | 
 | ||||||
| USERNAME = config['login_info']['username'] | USERNAME = config['login_info']['username'] | ||||||
|  | @ -44,6 +45,30 @@ def resize_image(image_path, cache_path, size=(800, 450)): | ||||||
|             img = img.convert("RGB") |             img = img.convert("RGB") | ||||||
|         img.save(cache_path, format="JPEG") |         img.save(cache_path, format="JPEG") | ||||||
| 
 | 
 | ||||||
|  | def delete_non_existing_cache(): | ||||||
|  |     cache_folder = app.config['CACHE_FOLDER'] | ||||||
|  |     upload_folder = app.config['UPLOAD_FOLDER'] | ||||||
|  |     dump_cache_folder = app.config['DUMP_CACHE_FOLDER'] | ||||||
|  |     dump_folder = app.config['DUMP_FOLDER'] | ||||||
|  | 
 | ||||||
|  |     images = os.listdir(cache_folder) | ||||||
|  |     for image in images: | ||||||
|  |         image_path_upload = os.path.join(upload_folder, image) | ||||||
|  |         cache_path = os.path.join(cache_folder, image) | ||||||
|  | 
 | ||||||
|  |         if not os.path.exists(image_path_upload): | ||||||
|  |             if os.path.exists(cache_path): | ||||||
|  |                 os.remove(cache_path) | ||||||
|  |      | ||||||
|  |     dump_images = os.listdir(dump_cache_folder) | ||||||
|  |     for image in dump_images: | ||||||
|  |         image_path_dump = os.path.join(dump_folder, image) | ||||||
|  |         dump_cache_path = os.path.join(dump_cache_folder, image) | ||||||
|  | 
 | ||||||
|  |         if not os.path.exists(image_path_dump): | ||||||
|  |             if os.path.exists(dump_cache_path): | ||||||
|  |                 os.remove(dump_cache_path) | ||||||
|  | 
 | ||||||
| def create_cache(): | def create_cache(): | ||||||
|     images = os.listdir(app.config['UPLOAD_FOLDER']) |     images = os.listdir(app.config['UPLOAD_FOLDER']) | ||||||
|     cached_images = [] |     cached_images = [] | ||||||
|  | @ -51,18 +76,47 @@ def create_cache(): | ||||||
|     if not os.path.exists(app.config['CACHE_FOLDER']): |     if not os.path.exists(app.config['CACHE_FOLDER']): | ||||||
|         os.makedirs(app.config['CACHE_FOLDER']) |         os.makedirs(app.config['CACHE_FOLDER']) | ||||||
| 
 | 
 | ||||||
|     for image in images: |     caches_path = set([os.path.join(app.config['CACHE_FOLDER'], image) for image in os.listdir(app.config['CACHE_FOLDER'])]) | ||||||
|         image_path = os.path.join(app.config['UPLOAD_FOLDER'], image) |     images_path = set([os.path.join(app.config['UPLOAD_FOLDER'], image) for image in images]) | ||||||
|         cache_path = os.path.join(app.config['CACHE_FOLDER'], image) |  | ||||||
| 
 |  | ||||||
|         if not os.path.exists(cache_path) and is_image_valid(image_path): |  | ||||||
|             resize_image(image_path, cache_path) |  | ||||||
| 
 | 
 | ||||||
|  |     # get the images that are not cached | ||||||
|  |     uncached_images = images_path - caches_path | ||||||
|  |     for image_path in uncached_images: | ||||||
|         if is_image_valid(image_path): |         if is_image_valid(image_path): | ||||||
|             cached_images.append(image) |             cache_path = os.path.join(app.config['CACHE_FOLDER'], os.path.basename(image_path)) | ||||||
|  |             resize_image(image_path, cache_path) | ||||||
|  |             cached_images.append(os.path.basename(image_path))  | ||||||
|      |      | ||||||
|     return cached_images |     return cached_images | ||||||
| 
 | 
 | ||||||
|  | def create_dump_cache(): | ||||||
|  |     images = os.listdir(app.config['DUMP_FOLDER']) | ||||||
|  |     cached_images = [] | ||||||
|  | 
 | ||||||
|  |     if not os.path.exists(app.config['DUMP_CACHE_FOLDER']): | ||||||
|  |         os.makedirs(app.config['DUMP_CACHE_FOLDER']) | ||||||
|  | 
 | ||||||
|  |     caches_path = set([os.path.join(app.config['DUMP_CACHE_FOLDER'], image) for image in os.listdir(app.config['DUMP_CACHE_FOLDER'])]) | ||||||
|  |     images_path = set([os.path.join(app.config['DUMP_FOLDER'], image) for image in images]) | ||||||
|  | 
 | ||||||
|  |     # get the images that are not cached | ||||||
|  |     uncached_images = images_path - caches_path | ||||||
|  |     for image_path in uncached_images: | ||||||
|  |         if is_image_valid(image_path): | ||||||
|  |             cache_path = os.path.join(app.config['DUMP_CACHE_FOLDER'], os.path.basename(image_path)) | ||||||
|  |             resize_image(image_path, cache_path) | ||||||
|  |             cached_images.append(os.path.basename(image_path))  | ||||||
|  |      | ||||||
|  |     return cached_images | ||||||
|  | 
 | ||||||
|  | def update_cache(): | ||||||
|  |     create_cache() | ||||||
|  |     delete_non_existing_cache() | ||||||
|  | 
 | ||||||
|  | def update_dump_cache(): | ||||||
|  |     create_dump_cache() | ||||||
|  |     delete_non_existing_cache() | ||||||
|  |      | ||||||
| @app.route('/') | @app.route('/') | ||||||
| def index(): | def index(): | ||||||
|     return redirect(url_for('login')) |     return redirect(url_for('login')) | ||||||
|  | @ -91,6 +145,7 @@ def gallery(): | ||||||
|         return redirect(url_for('login')) |         return redirect(url_for('login')) | ||||||
|      |      | ||||||
|     cached_images = create_cache() |     cached_images = create_cache() | ||||||
|  |     cached_images.sort() | ||||||
|     batches = [] |     batches = [] | ||||||
|     current_batch = [] |     current_batch = [] | ||||||
|     current_batch_number = None |     current_batch_number = None | ||||||
|  | @ -112,6 +167,9 @@ def gallery(): | ||||||
| 
 | 
 | ||||||
|     if current_batch: |     if current_batch: | ||||||
|         batches.append(current_batch) |         batches.append(current_batch) | ||||||
|  | 
 | ||||||
|  |     batches = batches[::-1] | ||||||
|  | 
 | ||||||
|     if unstructured_images: |     if unstructured_images: | ||||||
|         batches.append(unstructured_images) |         batches.append(unstructured_images) | ||||||
| 
 | 
 | ||||||
|  | @ -127,6 +185,17 @@ def uploaded_file(filename): | ||||||
|         return send_from_directory(app.config['UPLOAD_FOLDER'], filename) |         return send_from_directory(app.config['UPLOAD_FOLDER'], filename) | ||||||
|     else: |     else: | ||||||
|         return "Invalid image", 404 |         return "Invalid image", 404 | ||||||
|  |      | ||||||
|  | @app.route('/dump_images/<filename>') | ||||||
|  | def uploaded_dump_file(filename): | ||||||
|  |     if not session.get('logged_in'): | ||||||
|  |         return redirect(url_for('login')) | ||||||
|  |      | ||||||
|  |     image_path = os.path.join(app.config['DUMP_FOLDER'], filename) | ||||||
|  |     if is_image_valid(image_path): | ||||||
|  |         return send_from_directory(app.config['DUMP_FOLDER'], filename) | ||||||
|  |     else: | ||||||
|  |         return "Invalid image", 404 | ||||||
| 
 | 
 | ||||||
| @app.route('/cached/<filename>') | @app.route('/cached/<filename>') | ||||||
| def cached_file(filename): | def cached_file(filename): | ||||||
|  | @ -139,16 +208,24 @@ def cached_file(filename): | ||||||
|     else: |     else: | ||||||
|         return "Invalid image", 404 |         return "Invalid image", 404 | ||||||
| 
 | 
 | ||||||
|  | @app.route('/dump_cached/<filename>') | ||||||
|  | def dump_cached_file(filename): | ||||||
|  |     if not session.get('logged_in'): | ||||||
|  |         return redirect(url_for('login')) | ||||||
|  |      | ||||||
|  |     image_path = os.path.join(app.config['DUMP_CACHE_FOLDER'], filename) | ||||||
|  |     if is_image_valid(image_path): | ||||||
|  |         return send_from_directory(app.config['DUMP_CACHE_FOLDER'], filename) | ||||||
|  |     else: | ||||||
|  |         return "Invalid image", 404 | ||||||
|  | 
 | ||||||
| @app.route('/api/images') | @app.route('/api/images') | ||||||
| def api_images(): | def api_images(): | ||||||
|     if not session.get('logged_in'): |     if not session.get('logged_in'): | ||||||
|         return jsonify([]) |         return jsonify([]) | ||||||
| 
 | 
 | ||||||
|     images = sorted( |     images = sorted(os.listdir(app.config['UPLOAD_FOLDER']), reverse=True) | ||||||
|         os.listdir(app.config['UPLOAD_FOLDER']), |      | ||||||
|         key=lambda x: os.path.getctime(os.path.join(app.config['UPLOAD_FOLDER'], x)), |  | ||||||
|         reverse=True |  | ||||||
|     ) |  | ||||||
|     valid_images = [img for img in images if is_image_valid(os.path.join(app.config['UPLOAD_FOLDER'], img))] |     valid_images = [img for img in images if is_image_valid(os.path.join(app.config['UPLOAD_FOLDER'], img))] | ||||||
|     return jsonify(valid_images) |     return jsonify(valid_images) | ||||||
| 
 | 
 | ||||||
|  | @ -157,42 +234,51 @@ def dump(): | ||||||
|     if not session.get('logged_in'): |     if not session.get('logged_in'): | ||||||
|         return redirect(url_for('login')) |         return redirect(url_for('login')) | ||||||
|      |      | ||||||
|     dump_folder = app.config['DUMP_FOLDER'] # Folder to store dump images |     cached_images = create_dump_cache() | ||||||
|     images = sorted( |     cached_images.sort(reverse=True) | ||||||
|         os.listdir(dump_folder), | 
 | ||||||
|         key=lambda x: os.path.getctime(os.path.join(dump_folder, x)), |     valid_images = [img for img in cached_images if is_image_valid(os.path.join(app.config['DUMP_FOLDER'], img))] | ||||||
|         reverse=True |  | ||||||
|     ) |  | ||||||
|     valid_images = [img for img in images if is_image_valid(os.path.join(dump_folder, img))] |  | ||||||
|     return render_template('dump.html', images=valid_images) |     return render_template('dump.html', images=valid_images) | ||||||
| 
 | 
 | ||||||
| def emit_gallery_update(): | def emit_gallery_update(): | ||||||
|     socketio.emit('update_gallery') |     socketio.emit('update_gallery') | ||||||
| 
 | 
 | ||||||
| class Watcher(FileSystemEventHandler): | def emit_dump_update(): | ||||||
|     def on_modified(self, event): |     socketio.emit('update_dump') | ||||||
|         if not event.is_directory and event.src_path.startswith(app.config['UPLOAD_FOLDER']): |  | ||||||
|             create_cache() |  | ||||||
|             emit_gallery_update() |  | ||||||
| 
 | 
 | ||||||
|  | class Watcher(FileSystemEventHandler): | ||||||
|     def on_created(self, event): |     def on_created(self, event): | ||||||
|         if not event.is_directory and event.src_path.startswith(app.config['UPLOAD_FOLDER']): |         if not event.is_directory and event.src_path.startswith(app.config['UPLOAD_FOLDER']): | ||||||
|             create_cache() |             print(f"File created: {event.src_path}") | ||||||
|  |             update_cache() | ||||||
|             emit_gallery_update() |             emit_gallery_update() | ||||||
| 
 | 
 | ||||||
|  |         if not event.is_directory and event.src_path.startswith(app.config['DUMP_FOLDER']): | ||||||
|  |             print(f"Dump file created: {event.src_path}") | ||||||
|  |             update_dump_cache() | ||||||
|  |             emit_dump_update() | ||||||
|  | 
 | ||||||
|     def on_deleted(self, event): |     def on_deleted(self, event): | ||||||
|         if not event.is_directory and event.src_path.startswith(app.config['UPLOAD_FOLDER']): |         if not event.is_directory and event.src_path.startswith(app.config['UPLOAD_FOLDER']): | ||||||
|             create_cache() |             print(f"File deleted: {event.src_path}") | ||||||
|  |             update_cache() | ||||||
|             emit_gallery_update() |             emit_gallery_update() | ||||||
| 
 | 
 | ||||||
|  |         if not event.is_directory and event.src_path.startswith(app.config['DUMP_FOLDER']): | ||||||
|  |             print(f"Dump file deleted: {event.src_path}") | ||||||
|  |             update_dump_cache() | ||||||
|  |             emit_dump_update() | ||||||
|  | 
 | ||||||
| @socketio.on('connect') | @socketio.on('connect') | ||||||
| def handle_connect(): | def handle_connect(): | ||||||
|     if not session.get('logged_in'): |     if not session.get('logged_in'): | ||||||
|         disconnect() |         disconnect() | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  |     event_handler = Watcher() | ||||||
|     observer = Observer() |     observer = Observer() | ||||||
|     observer.schedule(Watcher(), path=app.config['UPLOAD_FOLDER'], recursive=False) |     observer.schedule(event_handler, path=app.config['UPLOAD_FOLDER'], recursive=False) | ||||||
|  |     observer.schedule(event_handler, path=app.config['DUMP_FOLDER'], recursive=False) | ||||||
|     observer.start() |     observer.start() | ||||||
|     try: |     try: | ||||||
|         context = ('cert.pem', 'key.pem')  # Replace with your self-signed certificate and key files |         context = ('cert.pem', 'key.pem')  # Replace with your self-signed certificate and key files | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 60 KiB | 
| Before Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 45 KiB | 
| After Width: | Height: | Size: 38 KiB | 
| After Width: | Height: | Size: 51 KiB | 
| After Width: | Height: | Size: 42 KiB | 
| After Width: | Height: | Size: 32 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 33 KiB | 
| After Width: | Height: | Size: 35 KiB | 
| After Width: | Height: | Size: 36 KiB | 
| After Width: | Height: | Size: 31 KiB | 
| After Width: | Height: | Size: 66 KiB | 
| After Width: | Height: | Size: 4.7 MiB | 
| After Width: | Height: | Size: 6.6 MiB | 
| After Width: | Height: | Size: 4.7 MiB | 
| After Width: | Height: | Size: 6.1 MiB | 
| After Width: | Height: | Size: 6.3 MiB | 
| After Width: | Height: | Size: 6.6 MiB | 
| After Width: | Height: | Size: 6.4 MiB | 
| After Width: | Height: | Size: 2.2 MiB | 
| After Width: | Height: | Size: 2.2 MiB | 
| After Width: | Height: | Size: 4.7 MiB | 
| Before Width: | Height: | Size: 15 MiB | 
| Before Width: | Height: | Size: 2.8 MiB | 
| Before Width: | Height: | Size: 322 KiB | 
							
								
								
									
										
											BIN
										
									
								
								images/5月壁紙.png
								
								
								
								
							
							
						
						| Before Width: | Height: | Size: 12 MiB | 
|  | @ -0,0 +1,4 @@ | ||||||
|  | Flask | ||||||
|  | requests | ||||||
|  | Pillow | ||||||
|  | duckduckgo_search | ||||||
|  | @ -16,6 +16,7 @@ form h1 { | ||||||
| } | } | ||||||
| form { | form { | ||||||
|     background-color: #fff; |     background-color: #fff; | ||||||
|  |     margin: 20px; | ||||||
|     padding: 30px; |     padding: 30px; | ||||||
|     border-radius: 10px; |     border-radius: 10px; | ||||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | ||||||
|  |  | ||||||
|  | @ -1,9 +1,24 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html> | <html> | ||||||
| <head> | <head> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>Pumpkin Gallery🎃</title> |     <title>Pumpkin Gallery🎃</title> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='gallery_styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='gallery_styles.css') }}"> | ||||||
|  |     <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script> | ||||||
|  |     <style> | ||||||
|  |         .gallery-container { | ||||||
|  |             display: grid; | ||||||
|  |             grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | ||||||
|  |             gap: 10px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .gallery-container img { | ||||||
|  |             width: 100%; | ||||||
|  |             height: auto; | ||||||
|  |             display: block; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
| </head> | </head> | ||||||
| <body> | <body> | ||||||
|     <div class="button-container"> |     <div class="button-container"> | ||||||
|  | @ -13,10 +28,39 @@ | ||||||
|     <h1>Pumpkin Gallery🎃</h1> |     <h1>Pumpkin Gallery🎃</h1> | ||||||
|     <div class="gallery-container"> |     <div class="gallery-container"> | ||||||
|         {% for image in images %} |         {% for image in images %} | ||||||
|             <a href="#" data-original="{{ url_for('uploaded_file', filename=image) }}" target="_blank"> |             <a href="#" data-original="{{ url_for('uploaded_dump_file', filename=image) }}" target="_blank"> | ||||||
|                 <img class="responsive-img" src="{{ url_for('cached_file', filename=image) }}" alt="{{ image }}"> |                 <img class="responsive-img" src="{{ url_for('dump_cached_file', filename=image) }}" alt="{{ image }}"> | ||||||
|             </a> |             </a> | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|     </div> |     </div> | ||||||
|  | 
 | ||||||
|  |     <script> | ||||||
|  |         var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port); | ||||||
|  | 
 | ||||||
|  |         socket.on('update_dump', function() { | ||||||
|  |             location.reload(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         document.addEventListener("DOMContentLoaded", function() { | ||||||
|  |             var images = document.querySelectorAll('.responsive-img'); | ||||||
|  |             images.forEach(function(img) { | ||||||
|  |                 img.addEventListener('load', function() { | ||||||
|  |                     img.classList.add('loaded'); | ||||||
|  |                 }); | ||||||
|  |                 if (img.complete) { | ||||||
|  |                     img.classList.add('loaded'); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             var links = document.querySelectorAll('.gallery-container a'); | ||||||
|  |             links.forEach(function(link) { | ||||||
|  |                 link.addEventListener('click', function(event) { | ||||||
|  |                     event.preventDefault(); | ||||||
|  |                     var originalUrl = link.getAttribute('data-original'); | ||||||
|  |                     window.open(originalUrl, '_blank'); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     </script> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html> | <html> | ||||||
| <head> | <head> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>Fencing Gallery🤺</title> |     <title>Fencing Gallery🤺</title> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='gallery_styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='gallery_styles.css') }}"> | ||||||
|  | @ -35,111 +36,8 @@ | ||||||
|     <script> |     <script> | ||||||
|         var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port); |         var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port); | ||||||
| 
 | 
 | ||||||
|         function parseImageName(image) { |  | ||||||
|             var parts = image.split('_'); |  | ||||||
|             if (parts.length === 2) { |  | ||||||
|                 var batchNumber = parseInt(parts[0], 10); |  | ||||||
|                 var index = parseInt(parts[1].split('.')[0], 10); |  | ||||||
|                 return { batchNumber: batchNumber, index: index }; |  | ||||||
|             } |  | ||||||
|             return { batchNumber: 0, index: 0 }; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function removeNonExistentImages(existingImages, images) { |  | ||||||
|             var galleryContainer = document.getElementById('gallery-container'); |  | ||||||
|             existingImages.forEach(function(image) { |  | ||||||
|                 if (!images.includes(image)) { |  | ||||||
|                     var imgElement = galleryContainer.querySelector(`img[alt="${image}"]`); |  | ||||||
|                     if (imgElement) { |  | ||||||
|                         imgElement.parentElement.classList.add('removed'); |  | ||||||
|                         setTimeout(() => { |  | ||||||
|                             imgElement.parentElement.remove(); |  | ||||||
|                         }, 500); // Match the duration of the fadeOut animation |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function addNewImages(existingImages, images) { |  | ||||||
|             var galleryContainer = document.getElementById('gallery-container'); |  | ||||||
|             var currentBatchContainer = null; |  | ||||||
|             var currentBatchNumber = null; |  | ||||||
| 
 |  | ||||||
|             images.forEach(function(image) { |  | ||||||
|                 if (!existingImages.includes(image)) { |  | ||||||
|                     var parsedImage = parseImageName(image); |  | ||||||
|                     var batchNumber = parsedImage.batchNumber; |  | ||||||
|                     var index = parsedImage.index; |  | ||||||
| 
 |  | ||||||
|                     if (currentBatchNumber !== batchNumber) { |  | ||||||
|                         currentBatchNumber = batchNumber; |  | ||||||
|                         currentBatchContainer = document.querySelector(`.batch-${batchNumber}`); |  | ||||||
|                         if (!currentBatchContainer) { |  | ||||||
|                             currentBatchContainer = document.createElement('div'); |  | ||||||
|                             currentBatchContainer.className = `batch-container batch-${batchNumber}`; |  | ||||||
|                             galleryContainer.appendChild(currentBatchContainer); |  | ||||||
| 
 |  | ||||||
|                             if (galleryContainer.children.length > 1) { |  | ||||||
|                                 var divider = document.createElement('hr'); |  | ||||||
|                                 galleryContainer.insertBefore(divider, currentBatchContainer); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     var anchor = document.createElement('a'); |  | ||||||
|                     anchor.href = '#'; |  | ||||||
|                     anchor.setAttribute('data-original', `/images/${image}`); |  | ||||||
|                     anchor.target = '_blank'; |  | ||||||
|                     anchor.classList.add('added'); |  | ||||||
| 
 |  | ||||||
|                     var img = document.createElement('img'); |  | ||||||
|                     img.className = `responsive-img index-${index}`; |  | ||||||
|                     img.alt = image; |  | ||||||
|                     img.src = `/cached/${image}`; |  | ||||||
| 
 |  | ||||||
|                     anchor.appendChild(img); |  | ||||||
| 
 |  | ||||||
|                     // Insert the image in the correct location based on the index |  | ||||||
|                     var inserted = false; |  | ||||||
|                     var children = currentBatchContainer.children; |  | ||||||
|                     for (var i = 0; i < children.length; i++) { |  | ||||||
|                         var childIndex = parseInt(children[i].querySelector('img').className.split('index-')[1], 10); |  | ||||||
|                         if (index < childIndex) { |  | ||||||
|                             currentBatchContainer.insertBefore(anchor, children[i]); |  | ||||||
|                             inserted = true; |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     if (!inserted) { |  | ||||||
|                         currentBatchContainer.appendChild(anchor); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     img.addEventListener('load', function() { |  | ||||||
|                         img.classList.add('loaded'); |  | ||||||
|                     }); |  | ||||||
|                     if (img.complete) { |  | ||||||
|                         img.classList.add('loaded'); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     anchor.addEventListener('click', function(event) { |  | ||||||
|                         event.preventDefault(); |  | ||||||
|                         var originalUrl = anchor.getAttribute('data-original'); |  | ||||||
|                         window.open(originalUrl, '_blank'); |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         socket.on('update_gallery', function() { |         socket.on('update_gallery', function() { | ||||||
|             fetch('/api/images') |             location.reload(); | ||||||
|                 .then(response => response.json()) |  | ||||||
|                 .then(images => { |  | ||||||
|                     var galleryContainer = document.getElementById('gallery-container'); |  | ||||||
|                     var existingImages = Array.from(galleryContainer.getElementsByTagName('img')).map(img => img.alt); |  | ||||||
| 
 |  | ||||||
|                     removeNonExistentImages(existingImages, images); |  | ||||||
|                     addNewImages(existingImages, images); |  | ||||||
|                 }); |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         document.addEventListener("DOMContentLoaded", function() { |         document.addEventListener("DOMContentLoaded", function() { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html> | <html> | ||||||
| <head> | <head> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <title>Login</title> |     <title>Login</title> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> | ||||||
|     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='login_styles.css') }}"> |     <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='login_styles.css') }}"> | ||||||
|  |  | ||||||