Compare commits
24 Commits
cb91b92555
...
v2.0
Author | SHA1 | Date | |
---|---|---|---|
c825798b13 | |||
b5375343a8 | |||
757e676d2d | |||
0079c166e8 | |||
ee6d4a1fa2 | |||
c6c3162b83 | |||
04c1214cd1 | |||
b03779b487 | |||
b5f8ceeb31 | |||
1591886505 | |||
a6b63c2d2b | |||
8a04fe5aa6 | |||
2cb171806c | |||
ded97700d9 | |||
8533ce72e9 | |||
b2ba1d7c7f | |||
5d238fcf33 | |||
7675b90909 | |||
a916c80c2a | |||
7a95ef0255 | |||
906699f023 | |||
643a729f94 | |||
a02da47e73 | |||
f7f2356510 |
@ -6,9 +6,8 @@ COPY requirements.txt .
|
|||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY build.py gallery.py VERSION /app/
|
||||||
COPY ./src/ ./src/
|
COPY ./src/ ./src/
|
||||||
COPY ./build.py ./build.py
|
|
||||||
COPY ./gallery.py ./gallery.py
|
|
||||||
COPY ./config /app/default
|
COPY ./config /app/default
|
||||||
COPY ./docker/.sh/entrypoint.sh /app/entrypoint.sh
|
COPY ./docker/.sh/entrypoint.sh /app/entrypoint.sh
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
22
README.MD
22
README.MD
@ -18,7 +18,7 @@ The project includes two thoughtfully designed themes—one modern, one minimali
|
|||||||
- **Typewriter** — [View Demo](https://typewriter.djeex.fr)
|
- **Typewriter** — [View Demo](https://typewriter.djeex.fr)
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This GitHub repository is a mirror of the primary source at [git.djeex.fr/Djeex/lumeex](https://git.djeex.fr/Djeex/lumeex). The main repository includes the full history, releases, and bug-checking assisted by an LLM.
|
> _This GitHub repository is a mirror of the primary source at [git.djeex.fr/Djeex/lumeex](https://git.djeex.fr/Djeex/lumeex). The main repository includes the full history and releases_.
|
||||||
|
|
||||||
|
|
||||||
## 📌 Table of Contents
|
## 📌 Table of Contents
|
||||||
@ -41,20 +41,26 @@ The project includes two thoughtfully designed themes—one modern, one minimali
|
|||||||
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
||||||
- Supports Google Fonts and locally hosted fonts
|
- Supports Google Fonts and locally hosted fonts
|
||||||
|
|
||||||
### No-Code Builder (YAML Based)
|
### No-Code Builder (WebUI Manager)
|
||||||
|
|
||||||
- Configure site info, SEO, colors, fonts, and more through simple YAML files
|
|
||||||
- Reference and tag photos without any coding required
|
<div align="center">
|
||||||
- *(Optional)* Automatically update photo references via script
|
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex-webui.png" alt="Lumeex Screenshot" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### Simple Build Process
|
|
||||||
|
|
||||||
- Compiles static site from YAML configuration files (themes, templates, fonts, colors)
|
- Configure site info, SEO, colors, fonts, and more through a simple convenient WebUI
|
||||||
|
- Add and tag your photo photos without any coding required
|
||||||
- Converts favicon automatically to all required formats
|
- Converts favicon automatically to all required formats
|
||||||
- Resizes social sharing thumbnails
|
- Resizes social sharing thumbnails
|
||||||
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
||||||
- *(Optional)* Converts images to WebP format for optimized performance
|
- *(Optional)* Converts images to WebP format for optimized performance
|
||||||
- Outputs a complete static website ready to deploy on any web server
|
- Build your static site in one click and get a zip archive or an output folder, ready to deploy to your preferred webserver
|
||||||
|
|
||||||
|
### Don't want a WebUI ?
|
||||||
|
|
||||||
|
- CLI process is documented
|
||||||
|
|
||||||
## 🐳 Docker or 🐍 Python Installation
|
## 🐳 Docker or 🐍 Python Installation
|
||||||
For comprehensive documentation on installation, configuration options, customization, and demos, please visit:
|
For comprehensive documentation on installation, configuration options, customization, and demos, please visit:
|
||||||
|
@ -43,16 +43,24 @@ start_server() {
|
|||||||
cat /tmp/build_logs_fifo >&2 &
|
cat /tmp/build_logs_fifo >&2 &
|
||||||
cat /tmp/build_logs_fifo2 >&2 &
|
cat /tmp/build_logs_fifo2 >&2 &
|
||||||
|
|
||||||
echo "Starting HTTP server on port 3000..."
|
echo "Starting preview HTTP server on port 3000..."
|
||||||
python3 -u -m http.server 3000 -d /app/output &
|
python3 -u -m http.server 3000 -d /app/output &
|
||||||
SERVER_PID=$!
|
SERVER_PID=$!
|
||||||
trap "echo 'Stopping server...'; kill -TERM $SERVER_PID 2>/dev/null; wait $SERVER_PID; exit 0" SIGINT SIGTERM
|
|
||||||
|
echo "Starting Lumeex Flask webui..."
|
||||||
|
python3 -u -m src.py.webui.webui &
|
||||||
|
WEBUI_PID=$!
|
||||||
|
|
||||||
|
trap "echo 'Stopping servers...'; kill -TERM $SERVER_PID $WEBUI_PID 2>/dev/null; wait $SERVER_PID $WEBUI_PID; exit 0" SIGINT SIGTERM
|
||||||
|
|
||||||
wait $SERVER_PID
|
wait $SERVER_PID
|
||||||
|
wait $WEBUI_PID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VERSION=$(cat VERSION)
|
||||||
if [ $# -eq 0 ]; then
|
if [ $# -eq 0 ]; then
|
||||||
echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}"
|
echo -e "${CYAN}╭───────────────────────────────────────────╮${NC}"
|
||||||
echo -e "${CYAN}│${NC} Lum${CYAN}eex${NC} - Version 1.3.1${NC} ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Lum${CYAN}eex${NC} - Version ${VERSION}${NC} ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}├───────────────────────────────────────────┤${NC}"
|
echo -e "${CYAN}├───────────────────────────────────────────┤${NC}"
|
||||||
echo -e "${CYAN}│${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Source: https://git.djeex.fr/Djeex/lumeex ${CYAN}│${NC}"
|
||||||
echo -e "${CYAN}│${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}│${NC}"
|
echo -e "${CYAN}│${NC} Mirror: https://github.com/Djeex/lumeex ${CYAN}│${NC}"
|
||||||
|
@ -7,4 +7,5 @@ services:
|
|||||||
- ../output:/app/output # mount output directory
|
- ../output:/app/output # mount output directory
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
- "5000:5000"
|
||||||
|
|
BIN
illustration/lumeex-webui.png
Normal file
BIN
illustration/lumeex-webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 620 KiB |
@ -21,12 +21,14 @@ STYLE_DIR = SRC_DIR / "src/public/style"
|
|||||||
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
||||||
SITE_FILE = SRC_DIR / "config/site.yaml"
|
SITE_FILE = SRC_DIR / "config/site.yaml"
|
||||||
THEMES_DIR = SRC_DIR / "config/themes"
|
THEMES_DIR = SRC_DIR / "config/themes"
|
||||||
|
VERSION_FILE = SRC_DIR / "VERSION"
|
||||||
|
with open(VERSION_FILE, "r") as vf:
|
||||||
|
build_version = vf.read().strip()
|
||||||
|
|
||||||
def build():
|
def build():
|
||||||
build_version = "v1.3.1"
|
|
||||||
logging.info("\n")
|
logging.info("\n")
|
||||||
logging.info("=" * 24)
|
logging.info("=" * 24)
|
||||||
logging.info(f"🚀 Lumeex builder {build_version}")
|
logging.info(f"🚀 Lumeex builder v{build_version}")
|
||||||
logging.info("=" * 24)
|
logging.info("=" * 24)
|
||||||
logging.info("\n === Starting build === ")
|
logging.info("\n === Starting build === ")
|
||||||
ensure_dir(BUILD_DIR)
|
ensure_dir(BUILD_DIR)
|
||||||
|
@ -18,6 +18,10 @@ from src.py.webui.upload import upload_bp
|
|||||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
|
||||||
# --- Flask app setup ---
|
# --- Flask app setup ---
|
||||||
|
VERSION_FILE = Path(__file__).resolve().parents[3] / "VERSION"
|
||||||
|
with open(VERSION_FILE, "r") as vf:
|
||||||
|
lumeex_version = vf.read().strip()
|
||||||
|
|
||||||
WEBUI_PATH = Path(__file__).parents[2] / "webui" # Path to static/templates
|
WEBUI_PATH = Path(__file__).parents[2] / "webui" # Path to static/templates
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
@ -68,6 +72,10 @@ def get_local_fonts(theme_name):
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_version():
|
||||||
|
return dict(lumeex_version=lumeex_version)
|
||||||
|
|
||||||
# --- Gallery & Hero API ---
|
# --- Gallery & Hero API ---
|
||||||
@app.route("/gallery-editor")
|
@app.route("/gallery-editor")
|
||||||
def gallery_editor():
|
def gallery_editor():
|
||||||
@ -479,5 +487,5 @@ def download_output_zip():
|
|||||||
|
|
||||||
# --- Run server ---
|
# --- Run server ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.info("Starting WebUI at http://127.0.0.1:5000")
|
logging.info("Starting WebUI at http://0.0.0.0:5000")
|
||||||
app.run(debug=True)
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
@ -1,48 +1,14 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
{% extends "template/base.html" %}
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
{% block title %}Lumeex - Gallery Editor{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Lumeex</title>
|
{% block content %}
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Top bar -->
|
|
||||||
<div class="nav-bar">
|
|
||||||
<div class="content-inner nav">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nav-btn">
|
|
||||||
<label for="nav-check">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="nav-links">
|
|
||||||
<ul class="nav-list">
|
|
||||||
<li class="nav-item"><a href="/gallery-editor">Gallery</a>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
|
|
||||||
<h1>Gallery editor</h1>
|
<h1>Gallery editor</h1>
|
||||||
|
|
||||||
<!-- Hero Upload Section -->
|
<!-- Hero Upload Section -->
|
||||||
<div class="upload-section">
|
<div class="section">
|
||||||
<h2>Title Carrousel</h2>
|
<h2>Title Carrousel</h2>
|
||||||
<p> Select photos to display in the Title Carrousel</p>
|
<p> Select photos to display in the Title Carrousel</p>
|
||||||
<div class="upload-actions-row">
|
<div class="upload-actions-row">
|
||||||
@ -56,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Gallery Upload Section -->
|
<!-- Gallery Upload Section -->
|
||||||
<div class="upload-section">
|
<div class="section">
|
||||||
<h2>Gallery</h2>
|
<h2>Gallery</h2>
|
||||||
<p> Select and tags photos to display in the Gallery</p>
|
<p> Select and tags photos to display in the Gallery</p>
|
||||||
<div class="upload-actions-row">
|
<div class="upload-actions-row">
|
||||||
@ -68,31 +34,37 @@
|
|||||||
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||||
<div id="gallery"></div>
|
<div id="gallery"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
|
<div class="section">
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
|
<h2>Steps</h2>
|
||||||
</div>
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
<!-- Delete confirmation modal -->
|
<ul id="stepper">
|
||||||
<div id="delete-modal" class="modal" style="display:none;">
|
<li><a class="step-active" href="/gallery-editor">Upload your photos</a></li>
|
||||||
<div class="modal-content">
|
<div></div>
|
||||||
<span id="delete-modal-close" class="modal-close">×</span>
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
<h3>Confirm Deletion</h3>
|
<div></div>
|
||||||
<p id="delete-modal-text">Are you sure you want to delete this image?</p>
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
<div class="modal-actions">
|
<div></div>
|
||||||
<button id="delete-modal-confirm" class="modal-btn danger">Delete</button>
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- Delete confirmation modal -->
|
||||||
|
<div id="delete-modal" class="modal" style="display:none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span id="delete-modal-close" class="modal-close">×</span>
|
||||||
|
<h3>Confirm Deletion</h3>
|
||||||
|
<p id="delete-modal-text">Are you sure you want to delete this image?</p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button id="delete-modal-confirm" class="modal-btn danger">Delete</button>
|
||||||
|
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
{% endblock %}
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
{% block scripts %}
|
||||||
<h3>✅ Build completed!</h3>
|
<script src="{{ url_for('static', filename='js/gallery-editor.js') }}"></script>
|
||||||
<p>Your files are available in the output folder.</p>
|
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
{% endblock %}
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
154
src/webui/img/favicon.svg
Normal file
154
src/webui/img/favicon.svg
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1000 1000">
|
||||||
|
<!-- Generator: Adobe Illustrator 29.7.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 8) -->
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.st0 {
|
||||||
|
fill: url(#Dégradé_sans_nom_265);
|
||||||
|
stroke: url(#Dégradé_sans_nom_33);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st0, .st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
fill: url(#Dégradé_sans_nom_269);
|
||||||
|
stroke: url(#Dégradé_sans_nom_334);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st2 {
|
||||||
|
fill: url(#Dégradé_sans_nom_268);
|
||||||
|
stroke: url(#Dégradé_sans_nom_333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st3 {
|
||||||
|
fill: url(#Dégradé_sans_nom_266);
|
||||||
|
stroke: url(#Dégradé_sans_nom_331);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st4 {
|
||||||
|
fill: url(#Dégradé_sans_nom_267);
|
||||||
|
stroke: url(#Dégradé_sans_nom_332);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st12 {
|
||||||
|
fill: url(#Dégradé_sans_nom_261);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st13 {
|
||||||
|
fill: url(#Dégradé_sans_nom_262);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st14 {
|
||||||
|
fill: url(#Dégradé_sans_nom_264);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st15 {
|
||||||
|
fill: url(#Dégradé_sans_nom_263);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st5 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2616);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3311);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st6 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2615);
|
||||||
|
stroke: url(#Dégradé_sans_nom_3310);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st16 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st17 {
|
||||||
|
fill: url(#Dégradé_sans_nom_26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st7 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2610);
|
||||||
|
stroke: url(#Dégradé_sans_nom_335);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st8 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2613);
|
||||||
|
stroke: url(#Dégradé_sans_nom_338);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st9 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2614);
|
||||||
|
stroke: url(#Dégradé_sans_nom_339);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st10 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2611);
|
||||||
|
stroke: url(#Dégradé_sans_nom_336);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st11 {
|
||||||
|
fill: url(#Dégradé_sans_nom_2612);
|
||||||
|
stroke: url(#Dégradé_sans_nom_337);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_26" data-name="Dégradé sans nom 26" x1="373.2" y1="159.5" x2="625.1" y2="411.5" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c3ec"/>
|
||||||
|
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_261" data-name="Dégradé sans nom 26" x1="143.1" y1="200.5" x2="395" y2="452.4" gradientTransform="translate(30.8 109.3)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_262" data-name="Dégradé sans nom 26" x1="81.3" y1="60.6" x2="333.2" y2="312.5" gradientTransform="translate(187.1 873.6) rotate(-90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_263" data-name="Dégradé sans nom 26" x1="-44.4" y1="16.5" x2="207.5" y2="268.4" gradientTransform="translate(705.4 808.2) rotate(-180)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_264" data-name="Dégradé sans nom 26" x1="-67.9" y1="-58.9" x2="184" y2="193" gradientTransform="translate(770.9 385.1) rotate(90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_265" data-name="Dégradé sans nom 26" x1="74.5" y1="560.2" x2="106.2" y2="591.8" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="74.2" y1="559.9" x2="106.5" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#55c3ec"/>
|
||||||
|
<stop offset="1" stop-color="#1d71b8" stop-opacity=".5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_266" data-name="Dégradé sans nom 26" x1="158.2" y1="648.8" x2="176.7" y2="667.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="157.8" y1="648.4" x2="177.1" y2="667.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_267" data-name="Dégradé sans nom 26" x1="210.2" y1="714.7" x2="249.8" y2="754.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="209.9" y1="714.3" x2="250.2" y2="754.6" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_268" data-name="Dégradé sans nom 26" x1="-54" y1="72.2" x2="-14.4" y2="111.8" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="-54.4" y1="71.9" x2="-14.1" y2="112.2" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_269" data-name="Dégradé sans nom 26" x1="485.1" y1="-9.2" x2="503.7" y2="9.4" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="484.8" y1="-9.6" x2="504" y2="9.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2610" data-name="Dégradé sans nom 26" x1="825.1" y1="682.3" x2="856.8" y2="713.9" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.8" y1="681.9" x2="857.1" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2611" data-name="Dégradé sans nom 26" x1="308.5" y1="356.5" x2="340.3" y2="388.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="308.1" y1="356.2" x2="340.7" y2="388.7" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2612" data-name="Dégradé sans nom 26" x1="540.4" y1="450.4" x2="559" y2="469" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="540.1" y1="450" x2="559.4" y2="469.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2613" data-name="Dégradé sans nom 26" x1="-336.4" y1="326.9" x2="-296.8" y2="366.5" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="-336.7" y1="326.5" x2="-296.4" y2="366.8" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2614" data-name="Dégradé sans nom 26" x1="115" y1="-29.9" x2="133.6" y2="-11.3" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="114.7" y1="-30.2" x2="134" y2="-11" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2615" data-name="Dégradé sans nom 26" x1="94.5" y1="304.2" x2="124.4" y2="334" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="94.2" y1="303.8" x2="124.7" y2="334.4" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_2616" data-name="Dégradé sans nom 26" x1="435.8" y1="254.1" x2="454.4" y2="272.7" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||||
|
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="435.5" y1="253.8" x2="454.8" y2="273.1" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||||
|
</defs>
|
||||||
|
<g id="Calque_1">
|
||||||
|
<circle class="st16" cx="499.5" cy="499.5" r="499.5"/>
|
||||||
|
</g>
|
||||||
|
<g id="Calque_2">
|
||||||
|
<g id="Calque_3">
|
||||||
|
<ellipse class="st17" cx="499.2" cy="285.5" rx="139.8" ry="209.5"/>
|
||||||
|
<ellipse class="st12" cx="299.9" cy="435.8" rx="139.8" ry="209.5" transform="translate(-207.3 586.3) rotate(-72)"/>
|
||||||
|
<ellipse class="st13" cx="373.6" cy="666.3" rx="209.5" ry="139.8" transform="translate(-385.1 576.9) rotate(-54)"/>
|
||||||
|
<ellipse class="st15" cx="623.9" cy="665.8" rx="139.8" ry="209.5" transform="translate(-272.2 493.9) rotate(-36)"/>
|
||||||
|
<ellipse class="st14" cx="703.9" cy="443.1" rx="209.5" ry="139.8" transform="translate(-94.9 211.2) rotate(-16)"/>
|
||||||
|
<circle class="st0" cx="90.4" cy="576" r="22.4"/>
|
||||||
|
<circle class="st3" cx="175.6" cy="607.9" r="13.1"/>
|
||||||
|
<circle class="st4" cx="140.8" cy="691.6" r="28"/>
|
||||||
|
<circle class="st2" cx="829.7" cy="602.6" r="28"/>
|
||||||
|
<circle class="st1" cx="908.9" cy="562.1" r="13.1"/>
|
||||||
|
<circle class="st7" cx="840.9" cy="698.1" r="22.4"/>
|
||||||
|
<circle class="st10" cx="466.1" cy="876.5" r="22.5"/>
|
||||||
|
<circle class="st11" cx="538.6" cy="839.8" r="13.1"/>
|
||||||
|
<circle class="st8" cx="686.1" cy="170.1" r="28"/>
|
||||||
|
<circle class="st9" cx="733.7" cy="247.7" r="13.1"/>
|
||||||
|
<circle class="st6" cx="236.9" cy="206.5" r="21.1"/>
|
||||||
|
<circle class="st5" cx="315.4" cy="164.9" r="13.1"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.6 KiB |
2
src/webui/img/gitea.svg
Normal file
2
src/webui/img/gitea.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#48cf51ff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Gitea icon</title><path d="M4.186 5.421C2.341 5.417-.13 6.59.006 9.531c.213 4.594 4.92 5.02 6.801 5.057.206.862 2.42 3.834 4.059 3.99h7.18c4.306-.286 7.53-13.022 5.14-13.07-3.953.186-6.296.28-8.305.296v3.975l-.626-.277-.004-3.696c-2.306-.001-4.336-.108-8.189-.298-.482-.003-1.154-.085-1.876-.087zm.261 1.625h.22c.262 2.355.688 3.732 1.55 5.836-2.2-.26-4.072-.899-4.416-3.285-.178-1.235.422-2.524 2.646-2.552zm8.557 2.315c.15.002.303.03.447.096l.749.323-.537.979a.672.597 0 0 0-.241.038.672.597 0 0 0-.405.764.672.597 0 0 0 .112.174l-.926 1.686a.672.597 0 0 0-.222.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.765.672.597 0 0 0-.158-.22l.902-1.642a.672.597 0 0 0 .293-.03.672.597 0 0 0 .213-.112c.348.146.633.265.838.366.308.152.417.253.45.365.033.11-.003.322-.177.694-.13.277-.345.67-.599 1.133a.672.597 0 0 0-.251.038.672.597 0 0 0-.405.764.672.597 0 0 0 .86.36.672.597 0 0 0 .404-.764.672.597 0 0 0-.137-.202c.251-.458.467-.852.606-1.148.188-.402.286-.701.2-.99-.086-.289-.35-.477-.7-.65-.23-.113-.517-.233-.86-.377a.672.597 0 0 0-.038-.239.672.597 0 0 0-.145-.209l.528-.963 2.924 1.263c.528.229.746.79.49 1.26l-2.01 3.68c-.257.469-.888.663-1.416.435l-4.137-1.788c-.528-.228-.747-.79-.49-1.26l2.01-3.679c.176-.323.53-.515.905-.53h.064z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
19
src/webui/img/github.svg
Normal file
19
src/webui/img/github.svg
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>github [#142]</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="#ffffffff">
|
||||||
|
<g id="icons" transform="translate(56.000000, 160.000000)">
|
||||||
|
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399" id="github-[#142]">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
31
src/webui/index.html
Normal file
31
src/webui/index.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "template/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Lumeex{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Static Gallery Generator</h1>
|
||||||
|
<p>Use this generator to create a static gallery from your photos. Then, upload the static files to your preferred web server.</p>
|
||||||
|
|
||||||
|
<!-- Hero Upload Section -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>Steps</h2>
|
||||||
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
|
<div class="stepper">
|
||||||
|
<ul id="stepper">
|
||||||
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -4,6 +4,21 @@
|
|||||||
* @param {string} type - "success" or "error".
|
* @param {string} type - "success" or "error".
|
||||||
* @param {number} duration - Duration in ms.
|
* @param {number} duration - Duration in ms.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Toast helpers ---
|
||||||
function showToast(message, type = "success", duration = 3000) {
|
function showToast(message, type = "success", duration = 3000) {
|
||||||
const container = document.getElementById("toast-container");
|
const container = document.getElementById("toast-container");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@ -21,24 +36,34 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// Get build button and modal elements
|
// Get build button and modal elements
|
||||||
const buildBtn = document.getElementById("build-btn");
|
const buildBtn = document.getElementById("build-btn");
|
||||||
|
const stepperBuildBtn = document.getElementById("stepper-build"); // Added for stepper build button
|
||||||
const buildModal = document.getElementById("build-success-modal");
|
const buildModal = document.getElementById("build-success-modal");
|
||||||
const buildModalClose = document.getElementById("build-success-modal-close");
|
const buildModalClose = document.getElementById("build-success-modal-close");
|
||||||
const downloadZipBtn = document.getElementById("download-zip-btn");
|
const downloadZipBtn = document.getElementById("download-zip-btn");
|
||||||
const zipLoader = document.getElementById("zip-loader");
|
const zipLoader = document.getElementById("zip-loader");
|
||||||
|
|
||||||
|
// Build action handler
|
||||||
|
async function handleBuildClick() {
|
||||||
|
showLoader("Building static site...");
|
||||||
|
// Trigger build on backend
|
||||||
|
const res = await fetch("/api/build", { method: "POST" });
|
||||||
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
|
if (result.status === "ok") {
|
||||||
|
// Show build success modal
|
||||||
|
if (buildModal) buildModal.style.display = "flex";
|
||||||
|
} else {
|
||||||
|
showToast(result.message || "❌ Build failed!", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle build button click
|
// Handle build button click
|
||||||
if (buildBtn) {
|
if (buildBtn) {
|
||||||
buildBtn.addEventListener("click", async () => {
|
buildBtn.addEventListener("click", handleBuildClick);
|
||||||
// Trigger build on backend
|
}
|
||||||
const res = await fetch("/api/build", { method: "POST" });
|
// Handle stepper-build button click
|
||||||
const result = await res.json();
|
if (stepperBuildBtn) {
|
||||||
if (result.status === "ok") {
|
stepperBuildBtn.addEventListener("click", handleBuildClick);
|
||||||
// Show build success modal
|
|
||||||
if (buildModal) buildModal.style.display = "flex";
|
|
||||||
} else {
|
|
||||||
showToast(result.message || "❌ Build failed!", "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle download zip button click
|
// Handle download zip button click
|
||||||
|
@ -92,6 +92,14 @@ function renderTags(imgIndex, tags) {
|
|||||||
input.placeholder = 'Add tag...';
|
input.placeholder = 'Add tag...';
|
||||||
inputContainer.appendChild(input);
|
inputContainer.appendChild(input);
|
||||||
|
|
||||||
|
// --- Validate button ---
|
||||||
|
const validateBtn = document.createElement('button');
|
||||||
|
validateBtn.textContent = '✔️';
|
||||||
|
validateBtn.className = 'validate-tag-btn';
|
||||||
|
validateBtn.style.display = 'none'; // hidden by default
|
||||||
|
validateBtn.style.marginLeft = '4px';
|
||||||
|
inputContainer.appendChild(validateBtn);
|
||||||
|
|
||||||
const suggestionBox = document.createElement('ul');
|
const suggestionBox = document.createElement('ul');
|
||||||
suggestionBox.className = 'suggestions';
|
suggestionBox.className = 'suggestions';
|
||||||
inputContainer.appendChild(suggestionBox);
|
inputContainer.appendChild(suggestionBox);
|
||||||
@ -148,8 +156,14 @@ function renderTags(imgIndex, tags) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
input.addEventListener('input', updateSuggestions);
|
input.addEventListener('input', () => {
|
||||||
input.addEventListener('focus', updateSuggestions);
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
|
||||||
|
});
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
input.addEventListener('keydown', (e) => {
|
input.addEventListener('keydown', (e) => {
|
||||||
const items = suggestionBox.querySelectorAll('li');
|
const items = suggestionBox.querySelectorAll('li');
|
||||||
@ -172,11 +186,13 @@ function renderTags(imgIndex, tags) {
|
|||||||
}
|
}
|
||||||
input.value = '';
|
input.value = '';
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
} else if ([' ', ','].includes(e.key)) {
|
} else if ([' ', ','].includes(e.key)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addTag(input.value);
|
addTag(input.value);
|
||||||
input.value = '';
|
input.value = '';
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -184,9 +200,20 @@ function renderTags(imgIndex, tags) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
suggestionBox.style.display = 'none';
|
suggestionBox.style.display = 'none';
|
||||||
input.value = '';
|
input.value = '';
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
}, 150);
|
}, 150);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Validate button action ---
|
||||||
|
validateBtn.onclick = () => {
|
||||||
|
if (input.value.trim()) {
|
||||||
|
addTag(input.value.trim());
|
||||||
|
input.value = '';
|
||||||
|
updateSuggestions();
|
||||||
|
validateBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
input.focus();
|
input.focus();
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
}
|
}
|
@ -12,6 +12,19 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// Form and menu logic
|
// Form and menu logic
|
||||||
const form = document.getElementById("site-info-form");
|
const form = document.getElementById("site-info-form");
|
||||||
@ -130,10 +143,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
thumbnailUpload.addEventListener("change", async (e) => {
|
thumbnailUpload.addEventListener("change", async (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
showLoader("Uploading thumbnail...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
const res = await fetch("/api/thumbnail/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/thumbnail/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
if (thumbnailInput) thumbnailInput.value = result.filename;
|
if (thumbnailInput) thumbnailInput.value = result.filename;
|
||||||
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
|
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
|
||||||
@ -183,12 +198,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
themeUpload.addEventListener("change", async (e) => {
|
themeUpload.addEventListener("change", async (e) => {
|
||||||
const files = Array.from(e.target.files);
|
const files = Array.from(e.target.files);
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
showLoader("Uploading theme...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
formData.append("files", file, file.webkitRelativePath || file.name);
|
formData.append("files", file, file.webkitRelativePath || file.name);
|
||||||
});
|
});
|
||||||
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme uploaded!", "success");
|
showToast("✅ Theme uploaded!", "success");
|
||||||
// Refresh theme select after upload
|
// Refresh theme select after upload
|
||||||
@ -239,12 +256,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
deleteThemeModalConfirm.onclick = async () => {
|
deleteThemeModalConfirm.onclick = async () => {
|
||||||
if (!themeToDelete) return;
|
if (!themeToDelete) return;
|
||||||
|
showLoader("Removing theme...");
|
||||||
const res = await fetch("/api/theme/remove", {
|
const res = await fetch("/api/theme/remove", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme: themeToDelete })
|
body: JSON.stringify({ theme: themeToDelete })
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme removed!", "success");
|
showToast("✅ Theme removed!", "success");
|
||||||
// Refresh theme select
|
// Refresh theme select
|
||||||
@ -379,7 +398,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
// Check if thumbnail is set before saving (uploaded or present in input)
|
// Check if thumbnail is set before saving (uploaded or present in input)
|
||||||
if (!thumbnailInput || !thumbnailInput.value) {
|
if (!thumbnailInput || !thumbnailInput.value) {
|
||||||
|
showLoader("Saving...");
|
||||||
showToast("❌ Thumbnail is required.", "error");
|
showToast("❌ Thumbnail is required.", "error");
|
||||||
|
hideLoader();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +438,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
intellectual_property: ipParagraphs
|
intellectual_property: ipParagraphs
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// --- REMOVE loader for save ---
|
||||||
|
// showLoader("Saving...");
|
||||||
const res = await fetch("/api/site-info", {
|
const res = await fetch("/api/site-info", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -31,6 +31,20 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Color Picker
|
||||||
function setupColorPicker(colorId, btnId, textId, initial) {
|
function setupColorPicker(colorId, btnId, textId, initial) {
|
||||||
const colorInput = document.getElementById(colorId);
|
const colorInput = document.getElementById(colorId);
|
||||||
const colorBtn = document.getElementById(btnId);
|
const colorBtn = document.getElementById(btnId);
|
||||||
@ -40,7 +54,6 @@ function setupColorPicker(colorId, btnId, textId, initial) {
|
|||||||
colorBtn.style.background = initial;
|
colorBtn.style.background = initial;
|
||||||
textInput.value = initial.toUpperCase();
|
textInput.value = initial.toUpperCase();
|
||||||
|
|
||||||
// Color input is positioned over the button and is clickable
|
|
||||||
colorInput.addEventListener("input", () => {
|
colorInput.addEventListener("input", () => {
|
||||||
colorBtn.style.background = colorInput.value;
|
colorBtn.style.background = colorInput.value;
|
||||||
textInput.value = colorInput.value.toUpperCase();
|
textInput.value = colorInput.value.toUpperCase();
|
||||||
@ -178,11 +191,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
showToast("Only .woff and .woff2 fonts are allowed.", "error");
|
showToast("Only .woff and .woff2 fonts are allowed.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
showLoader("Uploading font...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("theme", themeInfo.theme_name);
|
formData.append("theme", themeInfo.theme_name);
|
||||||
const res = await fetch("/api/font/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/font/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Font uploaded!", "success");
|
showToast("✅ Font uploaded!", "success");
|
||||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||||
@ -219,9 +234,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
};
|
};
|
||||||
deleteFontModalConfirm.onclick = async () => {
|
deleteFontModalConfirm.onclick = async () => {
|
||||||
if (!fontToDelete) return;
|
if (!fontToDelete) return;
|
||||||
|
showLoader("Removing font...");
|
||||||
const result = await removeFont(themeInfo.theme_name, fontToDelete);
|
const result = await removeFont(themeInfo.theme_name, fontToDelete);
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("Font removed!", "✅ success");
|
showToast("Font removed!", "success");
|
||||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||||
refreshLocalFonts();
|
refreshLocalFonts();
|
||||||
} else {
|
} else {
|
||||||
@ -272,11 +289,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
showToast("Invalid file type for favicon.", "error");
|
showToast("Invalid file type for favicon.", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
showLoader("Uploading favicon...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("theme", themeInfo.theme_name);
|
formData.append("theme", themeInfo.theme_name);
|
||||||
const res = await fetch("/api/favicon/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/favicon/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
faviconInput.value = result.filename;
|
faviconInput.value = result.filename;
|
||||||
updateFaviconPreview(`/themes/${themeInfo.theme_name}/${result.filename}?t=${Date.now()}`);
|
updateFaviconPreview(`/themes/${themeInfo.theme_name}/${result.filename}?t=${Date.now()}`);
|
||||||
@ -303,12 +322,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
deleteFaviconModalConfirm.onclick = async () => {
|
deleteFaviconModalConfirm.onclick = async () => {
|
||||||
|
showLoader("Removing favicon...");
|
||||||
const res = await fetch("/api/favicon/remove", {
|
const res = await fetch("/api/favicon/remove", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme: themeInfo.theme_name })
|
body: JSON.stringify({ theme: themeInfo.theme_name })
|
||||||
});
|
});
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
faviconInput.value = "";
|
faviconInput.value = "";
|
||||||
updateFaviconPreview("");
|
updateFaviconPreview("");
|
||||||
@ -335,13 +356,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
if (addGoogleFontBtn) {
|
if (addGoogleFontBtn) {
|
||||||
addGoogleFontBtn.addEventListener("click", async () => {
|
addGoogleFontBtn.addEventListener("click", async () => {
|
||||||
googleFonts.push({ family: "", weights: [] });
|
googleFonts.push({ family: "", weights: [] });
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -353,13 +372,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
const googleFontsFields = document.getElementById("google-fonts-fields");
|
const googleFontsFields = document.getElementById("google-fonts-fields");
|
||||||
if (googleFontsFields) {
|
if (googleFontsFields) {
|
||||||
// Save on blur for family/weights fields
|
|
||||||
googleFontsFields.addEventListener("blur", async (e) => {
|
googleFontsFields.addEventListener("blur", async (e) => {
|
||||||
if (
|
if (
|
||||||
e.target.name &&
|
e.target.name &&
|
||||||
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
|
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
|
||||||
) {
|
) {
|
||||||
// Update googleFonts array from the form fields
|
|
||||||
const fontFields = googleFontsFields.querySelectorAll(".input-field");
|
const fontFields = googleFontsFields.querySelectorAll(".input-field");
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
fontFields.forEach(field => {
|
fontFields.forEach(field => {
|
||||||
@ -368,13 +385,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
.split(",").map(w => w.trim()).filter(Boolean);
|
.split(",").map(w => w.trim()).filter(Boolean);
|
||||||
googleFonts.push({ family, weights });
|
googleFonts.push({ family, weights });
|
||||||
});
|
});
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -382,20 +397,17 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
renderGoogleFonts(googleFonts);
|
renderGoogleFonts(googleFonts);
|
||||||
refreshFontDropdowns();
|
refreshFontDropdowns();
|
||||||
}
|
}
|
||||||
}, true); // Use capture phase to catch blur from children
|
}, true);
|
||||||
|
|
||||||
// Delegate remove button click for Google Fonts
|
|
||||||
googleFontsFields.addEventListener("click", async (e) => {
|
googleFontsFields.addEventListener("click", async (e) => {
|
||||||
if (e.target.classList.contains("remove-google-font")) {
|
if (e.target.classList.contains("remove-google-font")) {
|
||||||
const idx = Number(e.target.dataset.idx);
|
const idx = Number(e.target.dataset.idx);
|
||||||
googleFonts.splice(idx, 1);
|
googleFonts.splice(idx, 1);
|
||||||
// Save immediately to backend
|
|
||||||
await fetch("/api/theme-google-fonts", {
|
await fetch("/api/theme-google-fonts", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||||
});
|
});
|
||||||
// Fetch updated theme info and refresh dropdowns
|
|
||||||
const updatedThemeInfo = await fetchThemeInfo();
|
const updatedThemeInfo = await fetchThemeInfo();
|
||||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||||
googleFonts.length = 0;
|
googleFonts.length = 0;
|
||||||
@ -406,9 +418,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form submit
|
|
||||||
document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
|
document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
showLoader("Saving theme...");
|
||||||
const data = {};
|
const data = {};
|
||||||
data.colors = {
|
data.colors = {
|
||||||
primary: document.getElementById("color-primary-text").value,
|
primary: document.getElementById("color-primary-text").value,
|
||||||
@ -445,6 +457,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
|
body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
|
||||||
});
|
});
|
||||||
|
hideLoader();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast("✅ Theme saved!", "success");
|
showToast("✅ Theme saved!", "success");
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,41 +1,64 @@
|
|||||||
|
// --- Loader helpers ---
|
||||||
|
function showLoader(text = "Uploading...") {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
document.getElementById("loader-text").textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function hideLoader() {
|
||||||
|
const loader = document.getElementById("global-loader");
|
||||||
|
if (loader) loader.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Upload gallery images ---
|
// --- Upload gallery images ---
|
||||||
document.getElementById('upload-gallery').addEventListener('change', async (e) => {
|
const galleryInput = document.getElementById('upload-gallery');
|
||||||
const files = e.target.files;
|
if (galleryInput) {
|
||||||
if (!files.length) return;
|
galleryInput.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
const formData = new FormData();
|
try {
|
||||||
for (const file of files) formData.append('files', file);
|
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
try {
|
hideLoader();
|
||||||
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
if (res.ok) {
|
||||||
const data = await res.json();
|
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
||||||
if (res.ok) {
|
if (typeof refreshGallery === "function") refreshGallery();
|
||||||
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
} else showToast('Error: ' + data.error, "error");
|
||||||
refreshGallery();
|
} catch(err) {
|
||||||
} else showToast('Error: ' + data.error, "error");
|
hideLoader();
|
||||||
} catch(err) {
|
console.error(err);
|
||||||
console.error(err);
|
showToast('Server error!', "error");
|
||||||
showToast('Server error!', "error");
|
} finally { e.target.value = ''; }
|
||||||
} finally { e.target.value = ''; }
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// --- Upload hero images ---
|
// --- Upload hero images ---
|
||||||
document.getElementById('upload-hero').addEventListener('change', async (e) => {
|
const heroInput = document.getElementById('upload-hero');
|
||||||
const files = e.target.files;
|
if (heroInput) {
|
||||||
if (!files.length) return;
|
heroInput.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading hero photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
const formData = new FormData();
|
try {
|
||||||
for (const file of files) formData.append('files', file);
|
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
try {
|
hideLoader();
|
||||||
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
if (res.ok) {
|
||||||
const data = await res.json();
|
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
||||||
if (res.ok) {
|
if (typeof refreshHero === "function") refreshHero();
|
||||||
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
} else showToast('Error: ' + data.error, "error");
|
||||||
refreshHero();
|
} catch(err) {
|
||||||
} else showToast('Error: ' + data.error, "error");
|
hideLoader();
|
||||||
} catch(err) {
|
console.error(err);
|
||||||
console.error(err);
|
showToast('Server error!', "error");
|
||||||
showToast('Server error!', "error");
|
} finally { e.target.value = ''; }
|
||||||
} finally { e.target.value = ''; }
|
});
|
||||||
});
|
}
|
@ -1,43 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "template/base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Lumeex - Site Info{% endblock %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<title>Lumeex</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Top bar -->
|
|
||||||
<div class="nav-bar">
|
|
||||||
<div class="content-inner nav">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nav-btn">
|
|
||||||
<label for="nav-check">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="nav-links">
|
|
||||||
<ul class="nav-list">
|
|
||||||
<li class="nav-item"><a href="/gallery-editor">Gallery</a>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div id="site-info" class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
<h1>Edit Site Info</h1>
|
<h1>Edit Site Info</h1>
|
||||||
<form id="site-info-form">
|
<form id="site-info-form">
|
||||||
<!-- Info Section -->
|
<!-- Info Section -->
|
||||||
@ -166,42 +132,51 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Steps</h2>
|
||||||
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
|
<ul id="stepper">
|
||||||
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a class="step-active" href="/site-info">Configure site info</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a href="/theme-editor">Customize your theme</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete confirmation modal (now outside .content-inner) -->
|
<!-- Delete thumbnail confirmation modal-->
|
||||||
<div id="delete-modal" class="modal" style="display:none;">
|
<div class="content-inner">
|
||||||
<div class="modal-content">
|
<div id="delete-modal" class="modal" style="display:none;">
|
||||||
<span id="delete-modal-close" class="modal-close">×</span>
|
<div class="modal-content">
|
||||||
<h3>Confirm Deletion</h3>
|
<span id="delete-modal-close" class="modal-close">×</span>
|
||||||
<p id="delete-modal-text">Are you sure you want to remove this thumbnail?</p>
|
<h3>Confirm Deletion</h3>
|
||||||
<div class="modal-actions">
|
<p id="delete-modal-text">Are you sure you want to remove this thumbnail?</p>
|
||||||
<button id="delete-modal-confirm" class="modal-btn danger">Remove</button>
|
<div class="modal-actions">
|
||||||
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
<button id="delete-modal-confirm" class="modal-btn danger">Remove</button>
|
||||||
|
<button id="delete-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete theme confirmation modal -->
|
<!-- Delete theme confirmation modal -->
|
||||||
<div id="delete-theme-modal" class="modal" style="display:none;">
|
<div class="content-inner">
|
||||||
<div class="modal-content">
|
<div id="delete-theme-modal" class="modal" style="display:none;">
|
||||||
<span id="delete-theme-modal-close" class="modal-close">×</span>
|
<div class="modal-content">
|
||||||
<h3>Confirm Theme Deletion</h3>
|
<span id="delete-theme-modal-close" class="modal-close">×</span>
|
||||||
<p id="delete-theme-modal-text">Are you sure you want to remove this theme?</p>
|
<h3>Confirm Theme Deletion</h3>
|
||||||
<div class="modal-actions">
|
<p id="delete-theme-modal-text">Are you sure you want to remove this theme?</p>
|
||||||
<button id="delete-theme-modal-confirm" class="modal-btn danger">Remove</button>
|
<div class="modal-actions">
|
||||||
<button id="delete-theme-modal-cancel" class="modal-btn">Cancel</button>
|
<button id="delete-theme-modal-confirm" class="modal-btn danger">Remove</button>
|
||||||
|
<button id="delete-theme-modal-cancel" class="modal-btn">Cancel</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
{% endblock %}
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
{% block scripts %}
|
||||||
<h3>✅ Build completed!</h3>
|
<script src="{{ url_for('static', filename='js/site-info.js') }}"></script>
|
||||||
<p>Your files are available in the output folder.</p>
|
{% endblock %}
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="{{ url_for('static', filename='js/site-info.js')}}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,18 +1,18 @@
|
|||||||
/* --- Base Styles --- */
|
/* --- Base Styles --- */
|
||||||
body {
|
body {
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
margin: 20px;
|
|
||||||
background: #111010;
|
background: #111010;
|
||||||
/* background:radial-gradient(ellipse at bottom center, #002a30, #000000bd), radial-gradient(ellipse at top center, #0558a8, #000000fa); */
|
|
||||||
color: #FBFBFB;
|
color: #FBFBFB;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
margin:0px;
|
margin:0px;
|
||||||
padding-top: 70px;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-inner {
|
a {
|
||||||
max-width: 90%;
|
text-decoration: none;
|
||||||
margin: 0 auto;
|
color: #d3d3d3;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
@ -22,37 +22,217 @@ h2 {
|
|||||||
color: #55c3ec;
|
color: #55c3ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Toolbar --- */
|
.content-inner {
|
||||||
.toolbar {
|
margin: 0 auto;
|
||||||
margin-bottom: 20px;
|
max-width: 1220px;
|
||||||
|
padding-top: 70px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
padding: 0 40px;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
margin-right: 10px;
|
/* --- Navbar & Burger Menu --- */
|
||||||
padding: 8px 12px;
|
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 70px;
|
||||||
|
background: #0c0d0c29;
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid #21212157;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 1140px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 40px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 70px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav img {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color:#fff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item a:hover {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #55c3ec;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list > li + li::before {
|
||||||
|
content: " → ";
|
||||||
|
color: #ffc700;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 10px 25px;
|
||||||
|
border-radius: 40px;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: linear-gradient(135deg, #26c4ff, #016074);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
transition: background 0.2s;
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button:hover {
|
.button:hover {
|
||||||
background-color: #45a049;
|
background: linear-gradient(135deg, #72d9ff, #26657e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Burger Menu --- */
|
||||||
|
.nav-burger {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: auto;
|
||||||
|
z-index: 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-burger span {
|
||||||
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 4px;
|
||||||
|
margin: 4px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: 0.3s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsive Navbar & Burger --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-burger {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list > li::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 24px 0 12px 0;
|
||||||
|
display: none;
|
||||||
|
z-index: 1099;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
background-color: #000000d4
|
||||||
|
}
|
||||||
|
.nav-links .nav-list {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.nav-links .nav-item {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.nav-links .nav-item a,
|
||||||
|
.nav-links .nav-item button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
border: none;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show menu when burger is checked */
|
||||||
|
.nav-toggle:checked ~ .nav-burger + .nav-links {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
/* Animate burger to X */
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(1) {
|
||||||
|
transform: translateY(12px) rotate(45deg);
|
||||||
|
}
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.nav-toggle:checked ~ .nav-burger span:nth-child(3) {
|
||||||
|
transform: translateY(-12px) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
/* --- Upload Section --- */
|
/* --- Upload Section --- */
|
||||||
.upload-section {
|
.section {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
background-color: rgb(67 67 67 / 26%);
|
background-color: rgb(67 67 67 / 26%);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
border: 1px solid #2f2e2e80;
|
border: 1px solid #2f2e2e80;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 0px 20px 20px 20px;
|
padding: 0px 20px 20px 20px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-section label {
|
.section label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,22 +283,6 @@ h2 {
|
|||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Responsive Adjustments --- */
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
body {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar button {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-section label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Toast Notifications --- */
|
/* --- Toast Notifications --- */
|
||||||
#toast-container {
|
#toast-container {
|
||||||
@ -155,7 +319,6 @@ h2 {
|
|||||||
/* --- Tags --- */
|
/* --- Tags --- */
|
||||||
.tag-input {
|
.tag-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap-reverse;
|
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@ -221,6 +384,24 @@ h2 {
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo button.validate-tag-btn {
|
||||||
|
border-radius: 30px;
|
||||||
|
border: none;
|
||||||
|
background: #049b3d;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
width: 35px;
|
||||||
|
border: 1px solid #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo button.validate-tag-btn:hover {
|
||||||
|
background: #02cb4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.suggestions li.selected {
|
.suggestions li.selected {
|
||||||
background-color: #007782;
|
background-color: #007782;
|
||||||
color: white;
|
color: white;
|
||||||
@ -249,150 +430,8 @@ h2 {
|
|||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Top Bar & Navigation --- */
|
/* --- Upload Buttons --- */
|
||||||
.nav {
|
.up-btn, .footer-links a{
|
||||||
height: 100%;
|
|
||||||
max-width: 1140px;
|
|
||||||
padding: 0 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 70px;
|
|
||||||
background-color: #0c0d0c29;
|
|
||||||
z-index: 1000;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-bottom: 1px solid #21212157;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav img {
|
|
||||||
height: 30px;
|
|
||||||
padding: 0px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-header {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-header > .nav-title {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #fff;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links {
|
|
||||||
display: inline;
|
|
||||||
float: right;
|
|
||||||
font-size: 14px;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list {
|
|
||||||
list-style-type: disc;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0px 15px 0px 15px;
|
|
||||||
text-decoration: none;
|
|
||||||
height: 100%;
|
|
||||||
font-weight: 700;
|
|
||||||
color:#fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a:hover {
|
|
||||||
color: #00b0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > .nav-links > .nav-list > .nav-item > a:active {
|
|
||||||
color: #00b0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > #nav-check {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list > li + li::before{
|
|
||||||
content: " → ";
|
|
||||||
color: #ffc700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-cta {
|
|
||||||
display: inline;
|
|
||||||
float: right;
|
|
||||||
height: 70px;
|
|
||||||
line-height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-cta > .arrow {
|
|
||||||
font-size: 12px;
|
|
||||||
display: inline;
|
|
||||||
color: #ffc700;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar .button {
|
|
||||||
padding: 10px 25px;
|
|
||||||
border-radius: 40px;
|
|
||||||
margin: 15px 20px 15px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
display: inline;
|
|
||||||
background: linear-gradient(135deg, #26c4ff, #016074);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 700;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-bar .button:hover {
|
|
||||||
background: linear-gradient(135deg, #72d9ff, #26657e);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links > ul {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn span {
|
|
||||||
display: block;
|
|
||||||
height: 4px;
|
|
||||||
width: 28px;
|
|
||||||
background: #fff;
|
|
||||||
margin: 4px 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
/* --- Custom Upload Buttons --- */
|
|
||||||
.up-btn {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #00000000;
|
background: #00000000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -403,12 +442,11 @@ h2 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
/* box-shadow: 0 4px 10px rgba(0,0,0,0.25);*/
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border: 1px solid #585858;
|
border: 1px solid #585858;
|
||||||
}
|
}
|
||||||
|
|
||||||
.up-btn:hover {
|
.up-btn:hover, .footer-links a:hover {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,18 +458,20 @@ h2 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: #131313;
|
background: #000000a3;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 2rem 2.5rem;
|
padding: 2rem 2.5rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 24px rgba(0,0,0,0.25);
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
||||||
min-width: 300px;
|
min-width: 200px;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
@ -483,11 +523,12 @@ h2 {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove All Buttons */
|
/* --- Remove All Buttons --- */
|
||||||
#remove-all-hero, #remove-all-gallery {
|
#remove-all-hero, #remove-all-gallery {
|
||||||
background: #2d2d2d;
|
background: #2d2d2d;
|
||||||
color: white;
|
color: white;
|
||||||
display: none;
|
display: none;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remove-all-gallery:hover,
|
#remove-all-gallery:hover,
|
||||||
@ -495,7 +536,6 @@ h2 {
|
|||||||
background: rgb(121, 26, 19);
|
background: rgb(121, 26, 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive: stack buttons vertically on small screens */
|
|
||||||
@media (max-width: 500px) {
|
@media (max-width: 500px) {
|
||||||
.upload-actions-row {
|
.upload-actions-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -504,16 +544,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Site Info --- */
|
/* --- Forms --- */
|
||||||
|
|
||||||
/* --- Site Info Form --- */
|
|
||||||
|
|
||||||
#site-info.content-inner, #theme-editor.content-inner {
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
max-width: 1140px;
|
|
||||||
padding: 0 40px 40px 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
background-color: rgb(67 67 67 / 26%);
|
background-color: rgb(67 67 67 / 26%);
|
||||||
@ -541,7 +572,7 @@ legend {
|
|||||||
|
|
||||||
.input-field {
|
.input-field {
|
||||||
flex: 1 1 calc(33.333% - 18px);
|
flex: 1 1 calc(33.333% - 18px);
|
||||||
min-width: 220px;
|
min-width: 150px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -573,6 +604,16 @@ label {
|
|||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#theme-editor-form input, #theme-editor-form textarea,#theme-editor-form select {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-editor-form .fields {
|
||||||
|
gap: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#site-info-form input::placeholder,
|
#site-info-form input::placeholder,
|
||||||
#theme-editor-form input::placeholder,
|
#theme-editor-form input::placeholder,
|
||||||
#site-info-form textarea::placeholder,
|
#site-info-form textarea::placeholder,
|
||||||
@ -618,7 +659,7 @@ img#thumbnail-preview {
|
|||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
padding: 12px 32px;
|
padding: 12px 32px;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
margin-top: 18px;
|
margin: 0 0 45px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 16px rgba(38,196,255,0.15);
|
box-shadow: 0 4px 16px rgba(38,196,255,0.15);
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
@ -632,7 +673,7 @@ img#thumbnail-preview {
|
|||||||
background: #00000000;
|
background: #00000000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 18px;
|
border-radius: 30px;
|
||||||
padding: 7px 18px;
|
padding: 7px 18px;
|
||||||
font-size: 0.98em;
|
font-size: 0.98em;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@ -646,20 +687,6 @@ img#thumbnail-preview {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
#site-info-form, #theme-editor-form {
|
|
||||||
padding: 18px 8px;
|
|
||||||
}
|
|
||||||
.fields,
|
|
||||||
fieldset {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
.input-field {
|
|
||||||
min-width: 100%;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#site-info-form button.remove-menu-item, #site-info-form button.remove-ip-paragraph, #theme-editor-form button.remove-menu-item, #theme-editor-form button.remove-ip-paragraph {
|
#site-info-form button.remove-menu-item, #site-info-form button.remove-ip-paragraph, #theme-editor-form button.remove-menu-item, #theme-editor-form button.remove-ip-paragraph {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
@ -691,19 +718,21 @@ img#thumbnail-preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.color-fields {
|
.fields.color-fields {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#theme-editor button.color-btn {
|
#theme-editor-form button.color-btn {
|
||||||
height: 100%;
|
height: 40px;
|
||||||
border-radius: 8px;
|
border-radius: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="color"].color-input {
|
input[type="color"].color-input {
|
||||||
@ -717,7 +746,7 @@ input[type="color"].color-input {
|
|||||||
opacity:0;
|
opacity:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset p {
|
fieldset p, .section p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #b3b3b3;
|
color: #b3b3b3;
|
||||||
@ -741,3 +770,270 @@ fieldset p {
|
|||||||
#theme-editor-form button[type="button"]#choose-font-btn {
|
#theme-editor-form button[type="button"]#choose-font-btn {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Stepper --- */
|
||||||
|
|
||||||
|
#stepper {
|
||||||
|
display: flex;
|
||||||
|
gap: 18px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li,
|
||||||
|
#stepper > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper > div::before {
|
||||||
|
content: "→";
|
||||||
|
color: #ffc700;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li a, #stepper li button, #stepper > div {
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 100px;
|
||||||
|
border: 1px solid #585858;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #111010;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li a, #stepper li button {
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li button#stepper-build {
|
||||||
|
background: linear-gradient(135deg, #26c4ff, #016074);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-weight: bold;
|
||||||
|
color:#fff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li button#stepper-build:hover {
|
||||||
|
background: linear-gradient(135deg, #72d9ff, #26657e);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
color:#fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li a:hover, #stepper li a.step-active {
|
||||||
|
color: #fff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: #277fa0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li button#stepper-build::before {
|
||||||
|
content: "🚀 ";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color:#d3d3d3
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper > div {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffc700;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stepper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Footer --- */
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: #0c0d0c29;
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-top: 1px solid #21212157;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer-links, .footer-links a {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lum-first {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.lum-second {
|
||||||
|
color: #55c3ec;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-credit .lum-first::before {
|
||||||
|
content: url(/img/favicon.svg);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer .content-inner {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-text {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Global Loader & Spinner --- */
|
||||||
|
#global-loader {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; width: 100vw; height: 100vh;
|
||||||
|
z-index: 99999;
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-loader.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-inner {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.9s cubic-bezier(.4,0,.2,1);
|
||||||
|
background: #0c0d0c29;
|
||||||
|
padding: 32px 48px;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 2px 24px #000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#global-loader.active .loader-inner {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-spinner {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border: 6px solid #55c3ec;
|
||||||
|
border-top: 6px solid #222;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader-text {
|
||||||
|
margin-top: 18px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
|
||||||
|
/* --- Responsive Adjustments --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.inner {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.section label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-items-list > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stepper li {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container, .footer-links {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ip-list > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#stepper > div {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#stepper > div::before {
|
||||||
|
content: "↓";
|
||||||
|
color: #ffc700;
|
||||||
|
}
|
||||||
|
#stepper > div {
|
||||||
|
/* Hide the default arrow */
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
#site-info-form, #theme-editor-form {
|
||||||
|
padding: 18px 8px;
|
||||||
|
}
|
||||||
|
.input-field {
|
||||||
|
min-width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#color-picker .input-field{
|
||||||
|
min-width: 170px;
|
||||||
|
}
|
||||||
|
#theme-editor-form .fields.color-fields {
|
||||||
|
gap: 0 8px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
86
src/webui/template/base.html
Normal file
86
src/webui/template/base.html
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}Lumeex{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||||
|
<meta name="darkreader-lock">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Top bar -->
|
||||||
|
<div class="nav-bar">
|
||||||
|
<div class="content-inner nav">
|
||||||
|
<div class="nav-header">
|
||||||
|
<a href="/" class="nav-title">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Burger toggle input and label -->
|
||||||
|
<input type="checkbox" id="nav-toggle" class="nav-toggle" hidden>
|
||||||
|
<label for="nav-toggle" class="nav-burger">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
<div class="nav-links">
|
||||||
|
<ul class="nav-list">
|
||||||
|
<li class="nav-item"><a href="/gallery-editor">Gallery</a></li>
|
||||||
|
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
||||||
|
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button id="build-btn" class="button">🚀 Build!</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Toast container for notifications -->
|
||||||
|
<div class="content-inner first-content">
|
||||||
|
<div class="inner">
|
||||||
|
<div id="toast-container"></div>
|
||||||
|
<!-- Page content -->
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<!-- Build success modal -->
|
||||||
|
<div class="content-inner">
|
||||||
|
<div id="build-success-modal" class="modal" style="display:none;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span id="build-success-modal-close" class="modal-close">×</span>
|
||||||
|
<h3>✅ Build completed!</h3>
|
||||||
|
<p>Your files are available in the output folder.</p>
|
||||||
|
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
||||||
|
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer -->
|
||||||
|
<div id="footer">
|
||||||
|
<div class="content-inner">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="footer-container">
|
||||||
|
<div class="footer-credit">
|
||||||
|
<p><a href="https//lumeex.djeex.fr"><span class="lum-first">Lum</span><span class="lum-second">eex</span> v{{ lumeex_version }}</a> — © 2025</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a class="footer-link documentation" href="https://lumeex.djeex.fr"><span class="icon"><img src="/img/favicon.svg"></span><span class="icon-text">Documentation</span></a>
|
||||||
|
<a class="footer-link gitea" href="https://gitea.com/Djeex/lumeex"><span class="icon"><img src="/img/gitea.svg"></span><span class="icon-text">Giteex</span></a>
|
||||||
|
<a class="footer-link github" href="https://github.com/Djeex/lumeex"><span class="icon"><img src="/img/github.svg"></span><span class="icon-text">Github</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Loader -->
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="loader-inner">
|
||||||
|
<div class="loader-spinner"></div>
|
||||||
|
<div id="loader-text">Uploading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,51 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "template/base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %}Lumeex - Theme Editor{% endblock %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta charset="UTF-8">
|
{% block content %}
|
||||||
<title>Theme Editor</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
<h1>Edit Theme</h1>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Top bar -->
|
|
||||||
<div class="nav-bar">
|
|
||||||
<div class="content-inner nav">
|
|
||||||
<input type="checkbox" id="nav-check">
|
|
||||||
<div class="nav-header">
|
|
||||||
<div class="nav-title">
|
|
||||||
<img src="{{ url_for('static', filename='img/logo.svg') }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="nav-btn">
|
|
||||||
<label for="nav-check">
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="nav-links">
|
|
||||||
<ul class="nav-list">
|
|
||||||
<li class="nav-item"><a href="/gallery-editor">Gallery</a>
|
|
||||||
<li class="nav-item"><a href="/site-info">Site info</a></li>
|
|
||||||
<li class="nav-item"><a href="/theme-editor">Theme info</a></li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<button id="build-btn" class="button">Build !</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Toast container for notifications -->
|
|
||||||
<div id="theme-editor" class="content-inner">
|
|
||||||
<div id="toast-container"></div>
|
|
||||||
<h1>Edit Theme</h1>
|
|
||||||
<!-- Show current theme -->
|
<!-- Show current theme -->
|
||||||
<div class="theme-info">
|
<div class="theme-info">
|
||||||
<strong>Current theme:</strong> <span id="current-theme"></span>
|
<strong>Current theme:</strong> <span id="current-theme"></span>
|
||||||
</div>
|
</div>
|
||||||
<form id="theme-editor-form">
|
<form id="theme-editor-form">
|
||||||
<!-- Colors Section -->
|
<!-- Colors Section -->
|
||||||
<fieldset>
|
<fieldset id="color-picker">
|
||||||
<h2>Colors</h2>
|
<h2>Colors</h2>
|
||||||
<p>Set the color values for your theme</p>
|
<p>Set the color values for your theme</p>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
@ -169,6 +135,20 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<button type="submit">Save Theme</button>
|
<button type="submit">Save Theme</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Steps</h2>
|
||||||
|
<p> Follow the steps to generate your static gallery</p>
|
||||||
|
<ul id="stepper">
|
||||||
|
<li><a href="/gallery-editor">Upload your photos</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a href="/site-info">Configure site info</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><a class="step-active" href="/theme-editor">Customize your theme</a></li>
|
||||||
|
<div></div>
|
||||||
|
<li><button id="stepper-build">Generate your static site!</button></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Delete confirmation modal for favicon -->
|
<!-- Delete confirmation modal for favicon -->
|
||||||
<div id="delete-favicon-modal" class="modal" style="display:none;">
|
<div id="delete-favicon-modal" class="modal" style="display:none;">
|
||||||
@ -194,17 +174,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Build success modal -->
|
|
||||||
<div id="build-success-modal" class="modal" style="display:none;">
|
{% endblock %}
|
||||||
<div class="modal-content">
|
|
||||||
<span id="build-success-modal-close" class="modal-close">×</span>
|
{% block scripts %}
|
||||||
<h3>✅ Build completed!</h3>
|
|
||||||
<p>Your files are available in the output folder.</p>
|
|
||||||
<button id="download-zip-btn" class="modal-btn">Download ZIP</button>
|
|
||||||
<div id="zip-loader" style="display:none;">Creating ZIP...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user