Gallery front

This commit is contained in:
Djeex
2025-08-16 10:29:51 +02:00
parent 41450837f2
commit 1b0b228273
17 changed files with 535 additions and 9 deletions

View File

@ -3,6 +3,7 @@ from pathlib import Path
from shutil import copyfile
def generate_css_variables(colors_dict, output_path):
"""Generate css variables for theme colors"""
css_lines = [":root {"]
for key, value in colors_dict.items():
css_lines.append(f" --color-{key.replace('_', '-')}: {value};")
@ -13,6 +14,7 @@ def generate_css_variables(colors_dict, output_path):
logging.info(f"[✓] CSS variables written to {output_path}")
def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
"""Generate css variables fonts"""
font_files = list(fonts_dir.glob("*"))
font_faces = {}
preload_links = []
@ -57,6 +59,7 @@ def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
return preload_links
def generate_google_fonts_link(fonts):
"""Generate src link for Google fonts"""
if not fonts:
return ""
families = []

View File

@ -10,6 +10,7 @@ GALLERY_DIR = Path("config/photos/gallery")
HERO_DIR = Path("config/photos/hero")
def load_yaml(path):
"""Load gallery config .yaml file"""
print(f"[→] Loading {path}...")
if not os.path.exists(path):
print(f"[✗] File not found: {path}")
@ -21,11 +22,13 @@ def load_yaml(path):
return data
def save_yaml(data, path):
"""Save modified gallery config .yaml file"""
with open(path, "w", encoding="utf-8") as f:
yaml.dump(data, f, sort_keys=False, allow_unicode=True)
print(f"[✓] Saved updated YAML to {path}")
def get_all_image_paths(directory):
"""Get the path to record for builded site"""
return sorted([
str(p.relative_to(directory.parent)).replace("\\", "/")
for p in directory.rglob("*")
@ -33,6 +36,7 @@ def get_all_image_paths(directory):
])
def update_gallery():
"""Update the gallery photo list"""
print("\n=== Updating gallery.yaml (gallery section) ===")
gallery = load_yaml(GALLERY_YAML)
@ -71,6 +75,7 @@ def update_gallery():
print("[✓] No changes to gallery.yaml (gallery)")
def update_hero():
"""Update the hero photo list"""
print("\n=== Updating gallery.yaml (hero section) ===")
gallery = load_yaml(GALLERY_YAML)

View File

@ -3,6 +3,7 @@ import logging
from pathlib import Path
def render_template(template_path, context):
"""Render html templates"""
with open(template_path, encoding="utf-8") as f:
content = f.read()
for key, value in context.items():
@ -11,6 +12,7 @@ def render_template(template_path, context):
return content
def render_gallery_images(images):
"""Render the photo gallery"""
html = ""
for img in images:
tags = " ".join(img.get("tags", []))
@ -24,6 +26,7 @@ def render_gallery_images(images):
return html
def generate_gallery_json_from_images(images, output_dir):
"""Generte the hero carrousel photo list"""
try:
img_list = [img["src"] for img in images]
output_path = output_dir / "data" / "gallery.json"
@ -35,6 +38,7 @@ def generate_gallery_json_from_images(images, output_dir):
logging.error(f"[✗] Error generating gallery JSON: {e}")
def generate_robots_txt(canonical_url, allowed_paths, output_dir):
"""Generate the robot.txt"""
robots_lines = ["User-agent: *"]
# Block everything by default
@ -62,6 +66,7 @@ def generate_robots_txt(canonical_url, allowed_paths, output_dir):
logging.error(f"[✗] Failed to write robots.txt: {e}")
def generate_sitemap_xml(canonical_url, allowed_paths, output_dir):
"""Generate the sitemap"""
urlset_start = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
urlset_end = '</urlset>\n'
urls = ""

View File

@ -4,6 +4,7 @@ from pathlib import Path
from shutil import copytree, rmtree, copyfile
def load_yaml(path):
"""Load gallery and site .yaml conf"""
if not path.exists():
logging.warning(f"[!] YAML file not found: {path}")
return {}
@ -11,6 +12,7 @@ def load_yaml(path):
return yaml.safe_load(f)
def load_theme_config(theme_name, themes_dir):
"""Load theme.yaml"""
theme_dir = themes_dir / theme_name
theme_config_path = theme_dir / "theme.yaml"
if not theme_config_path.exists():
@ -20,26 +22,25 @@ def load_theme_config(theme_name, themes_dir):
return theme_vars, theme_dir
def clear_dir(path: Path):
"""Clear the output dir"""
if not path.exists():
path.mkdir(parents=True)
return
# Remove all files and subdirectories inside path, but not path itself
for child in path.iterdir():
if child.is_file() or child.is_symlink():
child.unlink() # delete file or symlink
child.unlink()
elif child.is_dir():
rmtree(child) # delete directory and contents
# Then replace your ensure_dir with this:
rmtree(child)
def ensure_dir(path: Path):
"""Create the output dir if it does not exist"""
if not path.exists():
path.mkdir(parents=True)
else:
clear_dir(path)
def copy_assets(js_dir, style_dir, build_dir):
"""Copy public assets to output dir"""
for folder in [js_dir, style_dir]:
if folder.exists():
dest = build_dir / folder.name

0
src/py/webui/__init__.py Normal file
View File

50
src/py/webui/upload.py Normal file
View File

@ -0,0 +1,50 @@
import logging
from pathlib import Path
from flask import Blueprint, request, current_app
from werkzeug.utils import secure_filename
# Create a Flask blueprint for upload routes
upload_bp = Blueprint("upload", __name__)
# Allowed file extensions for uploads
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "webp"}
# Function to check if a file has an allowed extension
def allowed_file(filename: str) -> bool:
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
# Function to save uploaded file to a given folder
def save_uploaded_file(file, folder: Path):
folder.mkdir(parents=True, exist_ok=True) # Create folder if it doesn't exist
filename = secure_filename(file.filename) # Sanitize filename
file.save(folder / filename) # Save file to folder
logging.info(f"[✓] Uploaded {filename} to {folder}")
return filename # Return saved filename
# Route to handle photo uploads for gallery or hero
@upload_bp.route("/api/<section>/upload", methods=["POST"])
def upload_photo(section: str):
# Validate section
if section not in ["gallery", "hero"]:
return {"error": "Invalid section"}, 400
# Check if the request contains a file
if "file" not in request.files:
return {"error": "No file part"}, 400
file = request.files["file"]
# Check if a file was actually selected
if file.filename == "":
return {"error": "No selected file"}, 400
# Check file type and save it
if file and allowed_file(file.filename):
PHOTOS_DIR = current_app.config.get("PHOTOS_DIR") # Get base photos directory from config
if not PHOTOS_DIR:
return {"error": "Server misconfiguration"}, 500
folder = PHOTOS_DIR / section # Target folder (gallery or hero)
filename = save_uploaded_file(file, folder)
return {"status": "ok", "filename": filename}
# If file type is not allowed
return {"error": "File type not allowed"}, 400

112
src/py/webui/webui.py Normal file
View File

@ -0,0 +1,112 @@
import logging
from pathlib import Path
from flask import Flask, jsonify, request, send_from_directory, render_template
from src.py.builder.gallery_builder import (
GALLERY_YAML, GALLERY_DIR, HERO_DIR,
load_yaml, save_yaml, get_all_image_paths, update_gallery, update_hero
)
from src.py.webui.upload import upload_bp
# --- Logging configuration ---
# Logs messages to console with INFO level
logging.basicConfig(level=logging.INFO, format="%(message)s")
# --- Flask app setup ---
# WEBUI_PATH points to the web UI folder (templates + static)
WEBUI_PATH = Path(__file__).parents[2] / "webui"
app = Flask(
__name__,
template_folder=WEBUI_PATH, # where Flask looks for templates
static_folder=WEBUI_PATH, # where Flask serves static files
static_url_path="" # URL path prefix for static files
)
# --- Absolute photos directory ---
# Used by upload.py and deletion endpoints
PHOTOS_DIR = Path(__file__).resolve().parents[3] / "config" / "photos"
app.config["PHOTOS_DIR"] = PHOTOS_DIR
# --- Register upload blueprint ---
# Handles /api/<section>/upload endpoints for gallery and hero images
app.register_blueprint(upload_bp)
# --- Existing API routes ---
# Serve main page
@app.route("/")
def index():
return render_template("index.html")
# Get gallery images (returns JSON array)
@app.route("/api/gallery", methods=["GET"])
def get_gallery():
data = load_yaml(GALLERY_YAML)
return jsonify(data.get("gallery", {}).get("images", []))
# Get hero images (returns JSON array)
@app.route("/api/hero", methods=["GET"])
def get_hero():
data = load_yaml(GALLERY_YAML)
return jsonify(data.get("hero", {}).get("images", []))
# Update gallery images with new JSON data
@app.route("/api/gallery/update", methods=["POST"])
def update_gallery_api():
images = request.json
data = load_yaml(GALLERY_YAML)
data["gallery"]["images"] = images
save_yaml(data, GALLERY_YAML)
return jsonify({"status": "ok"})
# Update hero images with new JSON data
@app.route("/api/hero/update", methods=["POST"])
def update_hero_api():
images = request.json
data = load_yaml(GALLERY_YAML)
data["hero"]["images"] = images
save_yaml(data, GALLERY_YAML)
return jsonify({"status": "ok"})
# Refresh gallery from the folder (rebuild YAML)
@app.route("/api/gallery/refresh", methods=["POST"])
def refresh_gallery():
update_gallery()
return jsonify({"status": "ok"})
# Refresh hero images from the folder
@app.route("/api/hero/refresh", methods=["POST"])
def refresh_hero():
update_hero()
return jsonify({"status": "ok"})
# Delete a gallery image file
@app.route("/api/gallery/delete", methods=["POST"])
def delete_gallery_photo():
data = request.json
src = data.get("src") # filename only
file_path = PHOTOS_DIR / "gallery" / src
if file_path.exists():
file_path.unlink() # remove the file
return {"status": "ok"}
return {"error": "File not found"}, 404
# Delete a hero image file
@app.route("/api/hero/delete", methods=["POST"])
def delete_hero_photo():
data = request.json
src = data.get("src") # filename only
file_path = PHOTOS_DIR / "hero" / src
if file_path.exists():
file_path.unlink() # remove the file
return {"status": "ok"}
return {"error": "File not found"}, 404
# Serve photos from /photos/<section>/<filename>
@app.route("/photos/<section>/<path:filename>")
def photos(section, filename):
return send_from_directory(PHOTOS_DIR / section, filename)
# --- Main entry point ---
if __name__ == "__main__":
logging.info("Starting WebUI at http://127.0.0.1:5000")
app.run(debug=True)