Compare commits
30 Commits
cb91b92555
...
v2.0.1
Author | SHA1 | Date | |
---|---|---|---|
df96782500 | |||
febf4d2be5 | |||
617545e1bb | |||
922ce99679 | |||
cd0428990a | |||
d3af86be8c | |||
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
|
||||
|
||||
COPY build.py gallery.py VERSION /app/
|
||||
COPY ./src/ ./src/
|
||||
COPY ./build.py ./build.py
|
||||
COPY ./gallery.py ./gallery.py
|
||||
COPY ./config /app/default
|
||||
COPY ./docker/.sh/entrypoint.sh /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)
|
||||
|
||||
> [!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
|
||||
@ -41,20 +41,26 @@ The project includes two thoughtfully designed themes—one modern, one minimali
|
||||
- Typewriter — [Demo](https://typewriter.djeex.fr)
|
||||
- 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
|
||||
- *(Optional)* Automatically update photo references via script
|
||||
|
||||
<div align="center">
|
||||
<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
|
||||
- Resizes social sharing thumbnails
|
||||
- *(Optional)* Automatically resizes photos to a maximum width of 1140px
|
||||
- *(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
|
||||
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_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 &
|
||||
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 $WEBUI_PID
|
||||
}
|
||||
|
||||
VERSION=$(cat VERSION)
|
||||
if [ $# -eq 0 ]; then
|
||||
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} Source: https://git.djeex.fr/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
|
||||
ports:
|
||||
- "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 |
@ -3,6 +3,13 @@
|
||||
|
||||
// Fade in effect for elements with class 'appear'
|
||||
const setupIntersectionObserver = () => {
|
||||
document.querySelectorAll('.appear').forEach(parent => {
|
||||
const children = parent.querySelectorAll('.appear');
|
||||
children.forEach((child, i) => {
|
||||
child.style.transitionDelay = `${i * 0.2}s`;
|
||||
});
|
||||
});
|
||||
|
||||
const items = document.querySelectorAll('.appear');
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
@ -32,6 +39,7 @@ const randomizeHeroBackground = () => {
|
||||
if (images.length === 0) return;
|
||||
let currentIndex = Math.floor(Math.random() * images.length);
|
||||
heroBg.style.backgroundImage = `url(/img/${images[currentIndex]})`;
|
||||
if (images.length < 2) return; // <-- Prevent interval if only one image
|
||||
setInterval(() => {
|
||||
let nextIndex;
|
||||
do {
|
||||
|
@ -192,36 +192,6 @@ h2 {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(1) {
|
||||
-webkit-transition-delay: 0s;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(2) {
|
||||
-webkit-transition-delay: 0.2s;
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(3) {
|
||||
-webkit-transition-delay: 0.4s;
|
||||
transition-delay: 0.4s;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(4) {
|
||||
-webkit-transition-delay: 0.6s;
|
||||
transition-delay: 0.6s;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(5) {
|
||||
-webkit-transition-delay: 0.8s;
|
||||
transition-delay: 0.8s;
|
||||
}
|
||||
|
||||
.appear.inview:nth-child(6) {
|
||||
-webkit-transition-delay: 1s;
|
||||
transition-delay: 1s;
|
||||
}
|
||||
|
||||
/* img fade in */
|
||||
|
||||
.fade-in-img {
|
||||
@ -491,7 +461,7 @@ h2 {
|
||||
}
|
||||
|
||||
.section img {
|
||||
margin: 0px 0 60px 0;
|
||||
margin: 0px 0 40px 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
|
@ -21,12 +21,14 @@ STYLE_DIR = SRC_DIR / "src/public/style"
|
||||
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
||||
SITE_FILE = SRC_DIR / "config/site.yaml"
|
||||
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():
|
||||
build_version = "v1.3.1"
|
||||
logging.info("\n")
|
||||
logging.info("=" * 24)
|
||||
logging.info(f"🚀 Lumeex builder {build_version}")
|
||||
logging.info(f"🚀 Lumeex builder v{build_version}")
|
||||
logging.info("=" * 24)
|
||||
logging.info("\n === Starting build === ")
|
||||
ensure_dir(BUILD_DIR)
|
||||
|
@ -18,6 +18,10 @@ from src.py.webui.upload import upload_bp
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
|
||||
# --- 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
|
||||
app = Flask(
|
||||
__name__,
|
||||
@ -68,6 +72,10 @@ def get_local_fonts(theme_name):
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
|
||||
@app.context_processor
|
||||
def inject_version():
|
||||
return dict(lumeex_version=lumeex_version)
|
||||
|
||||
# --- Gallery & Hero API ---
|
||||
@app.route("/gallery-editor")
|
||||
def gallery_editor():
|
||||
@ -479,5 +487,5 @@ def download_output_zip():
|
||||
|
||||
# --- Run server ---
|
||||
if __name__ == "__main__":
|
||||
logging.info("Starting WebUI at http://127.0.0.1:5000")
|
||||
app.run(debug=True)
|
||||
logging.info("Starting WebUI at http://0.0.0.0:5000")
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="inner bottom-link appear">
|
||||
<p><span class="navigation-subtitle appear">{{ copyright }}</span><span class="nav-separator"> • </span><span class="navigation-bottom-link appear"><a href="{{ legal_link }}">{{ legal_label }}</a></span></p>
|
||||
<p class="navigation-subtitle appear"> Built with <a href="https://git.djeex.fr/Djeex/lumeex">Lumeex</a></p>
|
||||
<p class="navigation-subtitle appear"> Built with <a href="https://lumeex.djeex.fr">Lumeex</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,48 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<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 class="content-inner">
|
||||
<div id="toast-container"></div>
|
||||
|
||||
{% extends "template/base.html" %}
|
||||
|
||||
{% block title %}Lumeex - Gallery Editor{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Gallery editor</h1>
|
||||
|
||||
<!-- Hero Upload Section -->
|
||||
<div class="upload-section">
|
||||
<div class="section">
|
||||
<h2>Title Carrousel</h2>
|
||||
<p> Select photos to display in the Title Carrousel</p>
|
||||
<div class="upload-actions-row">
|
||||
@ -56,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Gallery Upload Section -->
|
||||
<div class="upload-section">
|
||||
<div class="section">
|
||||
<h2>Gallery</h2>
|
||||
<p> Select and tags photos to display in the Gallery</p>
|
||||
<div class="upload-actions-row">
|
||||
@ -68,9 +34,19 @@
|
||||
<input type="file" id="upload-gallery" accept=".png,.jpg,.jpeg,.webp" multiple hidden>
|
||||
<div id="gallery"></div>
|
||||
</div>
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
|
||||
|
||||
<div class="section">
|
||||
<h2>Steps</h2>
|
||||
<p> Follow the steps to generate your static gallery</p>
|
||||
<ul id="stepper">
|
||||
<li><a class="step-active" 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>
|
||||
<!-- Delete confirmation modal -->
|
||||
<div id="delete-modal" class="modal" style="display:none;">
|
||||
@ -84,15 +60,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Build success modal -->
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/gallery-editor.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/upload.js') }}"></script>
|
||||
{% endblock %}
|
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 {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) {
|
||||
const container = document.getElementById("toast-container");
|
||||
if (!container) return;
|
||||
@ -21,24 +36,34 @@ function showToast(message, type = "success", duration = 3000) {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Get build button and modal elements
|
||||
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 buildModalClose = document.getElementById("build-success-modal-close");
|
||||
const downloadZipBtn = document.getElementById("download-zip-btn");
|
||||
const zipLoader = document.getElementById("zip-loader");
|
||||
|
||||
// Handle build button click
|
||||
if (buildBtn) {
|
||||
buildBtn.addEventListener("click", async () => {
|
||||
// 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
|
||||
if (buildBtn) {
|
||||
buildBtn.addEventListener("click", handleBuildClick);
|
||||
}
|
||||
// Handle stepper-build button click
|
||||
if (stepperBuildBtn) {
|
||||
stepperBuildBtn.addEventListener("click", handleBuildClick);
|
||||
}
|
||||
|
||||
// Handle download zip button click
|
||||
|
@ -92,6 +92,14 @@ function renderTags(imgIndex, tags) {
|
||||
input.placeholder = 'Add tag...';
|
||||
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');
|
||||
suggestionBox.className = 'suggestions';
|
||||
inputContainer.appendChild(suggestionBox);
|
||||
@ -148,8 +156,14 @@ function renderTags(imgIndex, tags) {
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener('input', updateSuggestions);
|
||||
input.addEventListener('focus', updateSuggestions);
|
||||
input.addEventListener('input', () => {
|
||||
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) => {
|
||||
const items = suggestionBox.querySelectorAll('li');
|
||||
@ -172,11 +186,13 @@ function renderTags(imgIndex, tags) {
|
||||
}
|
||||
input.value = '';
|
||||
updateSuggestions();
|
||||
validateBtn.style.display = 'none';
|
||||
} else if ([' ', ','].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
addTag(input.value);
|
||||
input.value = '';
|
||||
updateSuggestions();
|
||||
validateBtn.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
@ -184,9 +200,20 @@ function renderTags(imgIndex, tags) {
|
||||
setTimeout(() => {
|
||||
suggestionBox.style.display = 'none';
|
||||
input.value = '';
|
||||
validateBtn.style.display = 'none';
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// --- Validate button action ---
|
||||
validateBtn.onclick = () => {
|
||||
if (input.value.trim()) {
|
||||
addTag(input.value.trim());
|
||||
input.value = '';
|
||||
updateSuggestions();
|
||||
validateBtn.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
input.focus();
|
||||
updateSuggestions();
|
||||
}
|
@ -12,6 +12,19 @@ function showToast(message, type = "success", duration = 3000) {
|
||||
}, 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", () => {
|
||||
// Form and menu logic
|
||||
const form = document.getElementById("site-info-form");
|
||||
@ -130,10 +143,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
thumbnailUpload.addEventListener("change", async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
showLoader("Uploading thumbnail...");
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
const res = await fetch("/api/thumbnail/upload", { method: "POST", body: formData });
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
if (thumbnailInput) thumbnailInput.value = result.filename;
|
||||
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
|
||||
@ -183,12 +198,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
themeUpload.addEventListener("change", async (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
if (files.length === 0) return;
|
||||
showLoader("Uploading theme...");
|
||||
const formData = new FormData();
|
||||
files.forEach(file => {
|
||||
formData.append("files", file, file.webkitRelativePath || file.name);
|
||||
});
|
||||
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
showToast("✅ Theme uploaded!", "success");
|
||||
// Refresh theme select after upload
|
||||
@ -239,12 +256,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
};
|
||||
deleteThemeModalConfirm.onclick = async () => {
|
||||
if (!themeToDelete) return;
|
||||
showLoader("Removing theme...");
|
||||
const res = await fetch("/api/theme/remove", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme: themeToDelete })
|
||||
});
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
showToast("✅ Theme removed!", "success");
|
||||
// Refresh theme select
|
||||
@ -379,7 +398,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Check if thumbnail is set before saving (uploaded or present in input)
|
||||
if (!thumbnailInput || !thumbnailInput.value) {
|
||||
showLoader("Saving...");
|
||||
showToast("❌ Thumbnail is required.", "error");
|
||||
hideLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -417,6 +438,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
intellectual_property: ipParagraphs
|
||||
}
|
||||
};
|
||||
// --- REMOVE loader for save ---
|
||||
// showLoader("Saving...");
|
||||
const res = await fetch("/api/site-info", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
@ -31,6 +31,20 @@ function showToast(message, type = "success", duration = 3000) {
|
||||
}, 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) {
|
||||
const colorInput = document.getElementById(colorId);
|
||||
const colorBtn = document.getElementById(btnId);
|
||||
@ -40,7 +54,6 @@ function setupColorPicker(colorId, btnId, textId, initial) {
|
||||
colorBtn.style.background = initial;
|
||||
textInput.value = initial.toUpperCase();
|
||||
|
||||
// Color input is positioned over the button and is clickable
|
||||
colorInput.addEventListener("input", () => {
|
||||
colorBtn.style.background = colorInput.value;
|
||||
textInput.value = colorInput.value.toUpperCase();
|
||||
@ -178,11 +191,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
showToast("Only .woff and .woff2 fonts are allowed.", "error");
|
||||
return;
|
||||
}
|
||||
showLoader("Uploading font...");
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("theme", themeInfo.theme_name);
|
||||
const res = await fetch("/api/font/upload", { method: "POST", body: formData });
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
showToast("✅ Font uploaded!", "success");
|
||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||
@ -219,9 +234,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
};
|
||||
deleteFontModalConfirm.onclick = async () => {
|
||||
if (!fontToDelete) return;
|
||||
showLoader("Removing font...");
|
||||
const result = await removeFont(themeInfo.theme_name, fontToDelete);
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
showToast("Font removed!", "✅ success");
|
||||
showToast("Font removed!", "success");
|
||||
localFonts = await fetchLocalFonts(themeInfo.theme_name);
|
||||
refreshLocalFonts();
|
||||
} else {
|
||||
@ -272,11 +289,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
showToast("Invalid file type for favicon.", "error");
|
||||
return;
|
||||
}
|
||||
showLoader("Uploading favicon...");
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("theme", themeInfo.theme_name);
|
||||
const res = await fetch("/api/favicon/upload", { method: "POST", body: formData });
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
faviconInput.value = result.filename;
|
||||
updateFaviconPreview(`/themes/${themeInfo.theme_name}/${result.filename}?t=${Date.now()}`);
|
||||
@ -303,12 +322,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
}
|
||||
};
|
||||
deleteFaviconModalConfirm.onclick = async () => {
|
||||
showLoader("Removing favicon...");
|
||||
const res = await fetch("/api/favicon/remove", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme: themeInfo.theme_name })
|
||||
});
|
||||
const result = await res.json();
|
||||
hideLoader();
|
||||
if (result.status === "ok") {
|
||||
faviconInput.value = "";
|
||||
updateFaviconPreview("");
|
||||
@ -335,13 +356,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (addGoogleFontBtn) {
|
||||
addGoogleFontBtn.addEventListener("click", async () => {
|
||||
googleFonts.push({ family: "", weights: [] });
|
||||
// Save immediately to backend
|
||||
await fetch("/api/theme-google-fonts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||
});
|
||||
// Fetch updated theme info and refresh dropdowns
|
||||
const updatedThemeInfo = await fetchThemeInfo();
|
||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||
googleFonts.length = 0;
|
||||
@ -353,13 +372,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
|
||||
const googleFontsFields = document.getElementById("google-fonts-fields");
|
||||
if (googleFontsFields) {
|
||||
// Save on blur for family/weights fields
|
||||
googleFontsFields.addEventListener("blur", async (e) => {
|
||||
if (
|
||||
e.target.name &&
|
||||
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
|
||||
) {
|
||||
// Update googleFonts array from the form fields
|
||||
const fontFields = googleFontsFields.querySelectorAll(".input-field");
|
||||
googleFonts.length = 0;
|
||||
fontFields.forEach(field => {
|
||||
@ -368,13 +385,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
.split(",").map(w => w.trim()).filter(Boolean);
|
||||
googleFonts.push({ family, weights });
|
||||
});
|
||||
// Save immediately to backend
|
||||
await fetch("/api/theme-google-fonts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||
});
|
||||
// Fetch updated theme info and refresh dropdowns
|
||||
const updatedThemeInfo = await fetchThemeInfo();
|
||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||
googleFonts.length = 0;
|
||||
@ -382,20 +397,17 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
renderGoogleFonts(googleFonts);
|
||||
refreshFontDropdowns();
|
||||
}
|
||||
}, true); // Use capture phase to catch blur from children
|
||||
}, true);
|
||||
|
||||
// Delegate remove button click for Google Fonts
|
||||
googleFontsFields.addEventListener("click", async (e) => {
|
||||
if (e.target.classList.contains("remove-google-font")) {
|
||||
const idx = Number(e.target.dataset.idx);
|
||||
googleFonts.splice(idx, 1);
|
||||
// Save immediately to backend
|
||||
await fetch("/api/theme-google-fonts", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
|
||||
});
|
||||
// Fetch updated theme info and refresh dropdowns
|
||||
const updatedThemeInfo = await fetchThemeInfo();
|
||||
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
|
||||
googleFonts.length = 0;
|
||||
@ -406,9 +418,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Form submit
|
||||
document.getElementById("theme-editor-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
showLoader("Saving theme...");
|
||||
const data = {};
|
||||
data.colors = {
|
||||
primary: document.getElementById("color-primary-text").value,
|
||||
@ -445,6 +457,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ theme_name: themeInfo.theme_name, theme_yaml: data })
|
||||
});
|
||||
hideLoader();
|
||||
if (res.ok) {
|
||||
showToast("✅ Theme saved!", "success");
|
||||
} 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 ---
|
||||
document.getElementById('upload-gallery').addEventListener('change', async (e) => {
|
||||
const galleryInput = document.getElementById('upload-gallery');
|
||||
if (galleryInput) {
|
||||
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);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
||||
const data = await res.json();
|
||||
hideLoader();
|
||||
if (res.ok) {
|
||||
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
||||
refreshGallery();
|
||||
if (typeof refreshGallery === "function") refreshGallery();
|
||||
} else showToast('Error: ' + data.error, "error");
|
||||
} catch(err) {
|
||||
hideLoader();
|
||||
console.error(err);
|
||||
showToast('Server error!', "error");
|
||||
} finally { e.target.value = ''; }
|
||||
});
|
||||
}
|
||||
|
||||
// --- Upload hero images ---
|
||||
document.getElementById('upload-hero').addEventListener('change', async (e) => {
|
||||
const heroInput = document.getElementById('upload-hero');
|
||||
if (heroInput) {
|
||||
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);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
||||
const data = await res.json();
|
||||
hideLoader();
|
||||
if (res.ok) {
|
||||
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
||||
refreshHero();
|
||||
if (typeof refreshHero === "function") refreshHero();
|
||||
} else showToast('Error: ' + data.error, "error");
|
||||
} catch(err) {
|
||||
hideLoader();
|
||||
console.error(err);
|
||||
showToast('Server error!', "error");
|
||||
} finally { e.target.value = ''; }
|
||||
});
|
||||
}
|
@ -1,43 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<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>
|
||||
{% extends "template/base.html" %}
|
||||
|
||||
{% block title %}Lumeex - Site Info{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Edit Site Info</h1>
|
||||
<form id="site-info-form">
|
||||
<!-- Info Section -->
|
||||
@ -166,8 +132,22 @@
|
||||
</fieldset>
|
||||
<button type="submit">Save</button>
|
||||
</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>
|
||||
<!-- Delete confirmation modal (now outside .content-inner) -->
|
||||
</div>
|
||||
<!-- Delete thumbnail confirmation modal-->
|
||||
<div class="content-inner">
|
||||
<div id="delete-modal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<span id="delete-modal-close" class="modal-close">×</span>
|
||||
@ -179,7 +159,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Delete theme confirmation modal -->
|
||||
<div class="content-inner">
|
||||
<div id="delete-theme-modal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<span id="delete-theme-modal-close" class="modal-close">×</span>
|
||||
@ -191,17 +173,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Build success modal -->
|
||||
<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>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/site-info.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
@ -1,18 +1,18 @@
|
||||
/* --- Base Styles --- */
|
||||
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;
|
||||
margin: 20px;
|
||||
background: #111010;
|
||||
/* background:radial-gradient(ellipse at bottom center, #002a30, #000000bd), radial-gradient(ellipse at top center, #0558a8, #000000fa); */
|
||||
color: #FBFBFB;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin:0px;
|
||||
padding-top: 70px;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
max-width: 90%;
|
||||
margin: 0 auto;
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #d3d3d3;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
@ -22,37 +22,217 @@ h2 {
|
||||
color: #55c3ec;
|
||||
}
|
||||
|
||||
/* --- Toolbar --- */
|
||||
.toolbar {
|
||||
margin-bottom: 20px;
|
||||
.content-inner {
|
||||
margin: 0 auto;
|
||||
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;
|
||||
padding: 8px 12px;
|
||||
|
||||
/* --- Navbar & Burger Menu --- */
|
||||
|
||||
.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;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.toolbar button:hover {
|
||||
background-color: #45a049;
|
||||
.button:hover {
|
||||
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 {
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
background-color: rgb(67 67 67 / 26%);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #2f2e2e80;
|
||||
border-radius: 8px;
|
||||
padding: 0px 20px 20px 20px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.upload-section label {
|
||||
.section label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -103,22 +283,6 @@ h2 {
|
||||
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-container {
|
||||
@ -155,7 +319,6 @@ h2 {
|
||||
/* --- Tags --- */
|
||||
.tag-input {
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-content: flex-start;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
@ -221,6 +384,24 @@ h2 {
|
||||
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 {
|
||||
background-color: #007782;
|
||||
color: white;
|
||||
@ -249,150 +430,8 @@ h2 {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
/* --- Top Bar & Navigation --- */
|
||||
.nav {
|
||||
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 {
|
||||
/* --- Upload Buttons --- */
|
||||
.up-btn, .footer-links a{
|
||||
display: inline-block;
|
||||
background: #00000000;
|
||||
color: #fff;
|
||||
@ -403,12 +442,11 @@ h2 {
|
||||
text-align: center;
|
||||
transition: all 0.1s ease;
|
||||
user-select: none;
|
||||
/* box-shadow: 0 4px 10px rgba(0,0,0,0.25);*/
|
||||
font-size: 14px;
|
||||
border: 1px solid #585858;
|
||||
}
|
||||
|
||||
.up-btn:hover {
|
||||
.up-btn:hover, .footer-links a:hover {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
@ -420,18 +458,20 @@ h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 30px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #131313;
|
||||
background: #000000a3;
|
||||
color: #fff;
|
||||
padding: 2rem 2.5rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
||||
min-width: 300px;
|
||||
min-width: 200px;
|
||||
max-width: 90vw;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
@ -483,11 +523,12 @@ h2 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Remove All Buttons */
|
||||
/* --- Remove All Buttons --- */
|
||||
#remove-all-hero, #remove-all-gallery {
|
||||
background: #2d2d2d;
|
||||
color: white;
|
||||
display: none;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
#remove-all-gallery:hover,
|
||||
@ -495,7 +536,6 @@ h2 {
|
||||
background: rgb(121, 26, 19);
|
||||
}
|
||||
|
||||
/* Responsive: stack buttons vertically on small screens */
|
||||
@media (max-width: 500px) {
|
||||
.upload-actions-row {
|
||||
flex-direction: column;
|
||||
@ -504,16 +544,7 @@ h2 {
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Site Info --- */
|
||||
|
||||
/* --- 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;
|
||||
}
|
||||
/* --- Forms --- */
|
||||
|
||||
fieldset {
|
||||
background-color: rgb(67 67 67 / 26%);
|
||||
@ -541,7 +572,7 @@ legend {
|
||||
|
||||
.input-field {
|
||||
flex: 1 1 calc(33.333% - 18px);
|
||||
min-width: 220px;
|
||||
min-width: 150px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
@ -573,6 +604,16 @@ label {
|
||||
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,
|
||||
#theme-editor-form input::placeholder,
|
||||
#site-info-form textarea::placeholder,
|
||||
@ -618,7 +659,7 @@ img#thumbnail-preview {
|
||||
border-radius: 30px;
|
||||
padding: 12px 32px;
|
||||
font-size: 1.1em;
|
||||
margin-top: 18px;
|
||||
margin: 0 0 45px 0;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 16px rgba(38,196,255,0.15);
|
||||
transition: background 0.2s;
|
||||
@ -632,7 +673,7 @@ img#thumbnail-preview {
|
||||
background: #00000000;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
border-radius: 30px;
|
||||
padding: 7px 18px;
|
||||
font-size: 0.98em;
|
||||
margin-top: 8px;
|
||||
@ -646,20 +687,6 @@ img#thumbnail-preview {
|
||||
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 {
|
||||
margin-top: 0px;
|
||||
@ -691,19 +718,21 @@ img#thumbnail-preview {
|
||||
}
|
||||
|
||||
|
||||
.color-fields {
|
||||
.fields.color-fields {
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
|
||||
#theme-editor button.color-btn {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
#theme-editor-form button.color-btn {
|
||||
height: 40px;
|
||||
border-radius: 40px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 4px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
input[type="color"].color-input {
|
||||
@ -717,7 +746,7 @@ input[type="color"].color-input {
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
fieldset p {
|
||||
fieldset p, .section p {
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: #b3b3b3;
|
||||
@ -741,3 +770,270 @@ fieldset p {
|
||||
#theme-editor-form button[type="button"]#choose-font-btn {
|
||||
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,43 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8">
|
||||
<title>Theme Editor</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="theme-editor" class="content-inner">
|
||||
<div id="toast-container"></div>
|
||||
{% extends "template/base.html" %}
|
||||
|
||||
{% block title %}Lumeex - Theme Editor{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Edit Theme</h1>
|
||||
<!-- Show current theme -->
|
||||
<div class="theme-info">
|
||||
@ -45,7 +11,7 @@
|
||||
</div>
|
||||
<form id="theme-editor-form">
|
||||
<!-- Colors Section -->
|
||||
<fieldset>
|
||||
<fieldset id="color-picker">
|
||||
<h2>Colors</h2>
|
||||
<p>Set the color values for your theme</p>
|
||||
<div class="fields">
|
||||
@ -169,6 +135,20 @@
|
||||
</fieldset>
|
||||
<button type="submit">Save Theme</button>
|
||||
</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>
|
||||
<!-- Delete confirmation modal for favicon -->
|
||||
<div id="delete-favicon-modal" class="modal" style="display:none;">
|
||||
@ -194,17 +174,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Build success modal -->
|
||||
<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>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user