Build system with zip download

This commit is contained in:
Djeex
2025-08-19 19:29:06 +02:00
parent 4ac176f8a9
commit e9a3a5a189
7 changed files with 320 additions and 102 deletions

View File

@ -10,12 +10,6 @@
<!-- Top bar -->
<div class="nav-bar">
<div class="content-inner nav">
<div class="nav-cta">
<div class="arrow"></div>
<a class="button" href="#" target="_blank">
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
</a>
</div>
<input type="checkbox" id="nav-check">
<div class="nav-header">
<div class="nav-title">
@ -31,9 +25,12 @@
</div>
<div class="nav-links">
<ul class="nav-list">
<li class="nav-item appear2"><a href="/site-info">Site info</a></li>
<li class="nav-item appear2"><a href="/theme-editor">Theme info</a></li>
<li class="nav-item appear2"><a href="#">Gallery</a>
<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>
@ -73,6 +70,7 @@
</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>
<!-- Delete confirmation modal -->
<div id="delete-modal" class="modal" style="display:none;">
@ -86,5 +84,15 @@
</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">&times;</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>

81
src/webui/js/build.js Normal file
View File

@ -0,0 +1,81 @@
/**
* Show a toast notification.
* @param {string} message - The message to display.
* @param {string} type - "success" or "error".
* @param {number} duration - Duration in ms.
*/
function showToast(message, type = "success", duration = 3000) {
const container = document.getElementById("toast-container");
if (!container) return;
const toast = document.createElement("div");
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(() => toast.classList.add("show"));
setTimeout(() => {
toast.classList.remove("show");
setTimeout(() => container.removeChild(toast), 300);
}, duration);
}
document.addEventListener("DOMContentLoaded", () => {
// Get build button and modal elements
const buildBtn = document.getElementById("build-btn");
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 () => {
// Trigger build on backend
const res = await fetch("/api/build", { method: "POST" });
const result = await res.json();
if (result.status === "ok") {
// Show build success modal
if (buildModal) buildModal.style.display = "flex";
} else {
showToast(result.message || "❌ Build failed!", "error");
}
});
}
// Handle download zip button click
if (downloadZipBtn) {
downloadZipBtn.addEventListener("click", async () => {
if (zipLoader) zipLoader.style.display = "block";
downloadZipBtn.disabled = true;
// Request zip creation and download from backend
const res = await fetch("/download-output-zip", { method: "POST" });
if (res.ok) {
const blob = await res.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "site_output.zip";
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} else {
showToast("❌ Error creating ZIP", "error");
}
if (zipLoader) zipLoader.style.display = "none";
downloadZipBtn.disabled = false;
});
}
// Modal close logic
if (buildModal && buildModalClose) {
buildModalClose.onclick = () => {
buildModal.style.display = "none";
};
window.onclick = function(event) {
if (event.target === buildModal) {
buildModal.style.display = "none";
}
};
}
});

View File

@ -63,7 +63,7 @@ document.addEventListener("DOMContentLoaded", () => {
div.style.gap = "8px";
div.style.marginBottom = "6px";
div.innerHTML = `
<textarea placeholder="Paragraph" style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea>
<textarea placeholder="Paragraph" required style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea>
<button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button>
`;
ipList.appendChild(div);
@ -129,9 +129,9 @@ document.addEventListener("DOMContentLoaded", () => {
if (result.status === "ok") {
if (thumbnailInput) thumbnailInput.value = result.filename;
updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
showToast("Thumbnail uploaded!", "success");
showToast("Thumbnail uploaded!", "success");
} else {
showToast("Error uploading thumbnail", "error");
showToast("Error uploading thumbnail", "error");
}
});
}
@ -159,9 +159,9 @@ document.addEventListener("DOMContentLoaded", () => {
if (result.status === "ok") {
if (thumbnailInput) thumbnailInput.value = "";
updateThumbnailPreview("");
showToast("Thumbnail removed!", "success");
showToast("Thumbnail removed!", "success");
} else {
showToast("Error removing thumbnail", "error");
showToast("Error removing thumbnail", "error");
}
deleteModal.style.display = "none";
};
@ -182,7 +182,7 @@ document.addEventListener("DOMContentLoaded", () => {
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
const result = await res.json();
if (result.status === "ok") {
showToast("Theme uploaded!", "success");
showToast("Theme uploaded!", "success");
// Refresh theme select after upload
fetch("/api/themes")
.then(res => res.json())
@ -196,7 +196,7 @@ document.addEventListener("DOMContentLoaded", () => {
});
});
} else {
showToast("Error uploading theme", "error");
showToast("Error uploading theme", "error");
}
});
}
@ -311,6 +311,12 @@ document.addEventListener("DOMContentLoaded", () => {
updateMenuItemsFromInputs();
updateIpParagraphsFromInputs();
// Check if thumbnail is set before saving (uploaded or present in input)
if (!thumbnailInput || !thumbnailInput.value) {
showToast("❌ Thumbnail is required.", "error");
return;
}
const build = {
theme: themeSelect ? themeSelect.value : "",
convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked),

View File

@ -10,12 +10,6 @@
<!-- Top bar -->
<div class="nav-bar">
<div class="content-inner nav">
<div class="nav-cta">
<div class="arrow"></div>
<a class="button" href="#" target="_blank">
<span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
</a>
</div>
<input type="checkbox" id="nav-check">
<div class="nav-header">
<div class="nav-title">
@ -31,9 +25,12 @@
</div>
<div class="nav-links">
<ul class="nav-list">
<li class="nav-item appear2"><a href="/site-info">Site info</a></li>
<li class="nav-item appear2"><a href="/theme-editor">Theme info</a></li>
<li class="nav-item appear2"><a href="#">Gallery</a></li>
<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>
@ -81,9 +78,10 @@
<label>Instagram URL</label>
<input type="text" name="social.instagram_url" placeholder="https://instagram.com/yourprofile" required>
<label class="thumbnail-form-label">Thumbnail</label>
<input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;" required>
<input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;">
<button type="button" id="choose-thumbnail-btn" class="up-btn">📸 Upload a photo</button>
<div class="thumbnail-form">
<input type="hidden" name="social.thumbnail" id="social-thumbnail">
<img id="thumbnail-preview" src="" alt="Thumbnail preview" style="max-width:100px;display:none;">
<button type="button" id="remove-thumbnail-btn" class="remove-btn up-btn danger" style="display:none;">Remove</button>
</div>
@ -120,15 +118,15 @@
<div class="fields">
<div class="input-field">
<label>Hoster Name</label>
<input type="text" name="legals.hoster_name" placeholder="Name">
<input type="text" name="legals.hoster_name" placeholder="Name" required>
</div>
<div class="input-field">
<label>Hoster Address</label>
<input type="text" name="legals.hoster_address" placeholder="Street, Postal Code, City, Country">
<input type="text" name="legals.hoster_address" placeholder="Street, Postal Code, City, Country" required>
</div>
<div class="input-field">
<label>Hoster Contact</label>
<input type="text" name="legals.hoster_contact" placeholder="Email or/and Phone">
<input type="text" name="legals.hoster_contact" placeholder="Email or/and Phone" required>
</div>
<div class="input-field" style="flex: 1 1 100%;">
<label>Intellectual Property</label>
@ -173,6 +171,17 @@
</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">&times;</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>
<script src="{{ url_for('static', filename='js/site-info.js')}}"></script>
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
</body>
</html>

View File

@ -7,6 +7,7 @@ body {
color: #FBFBFB;
min-height: 100vh;
margin:0px;
padding-top: 70px;
}
.content-inner {
@ -256,12 +257,14 @@ h2 {
}
.nav-bar {
height: 70px;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 70px;
background-color: #0c0d0c29;
position: relative;
z-index: 1;
backdrop-filter: blur(20px);
z-index: 1000;
backdrop-filter: blur(20px);
border-bottom: 1px solid #21212157;
}
@ -287,6 +290,15 @@ h2 {
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;
@ -345,7 +357,7 @@ h2 {
font-weight: 700;
}
.nav-cta > .button {
.nav-bar .button {
padding: 10px 25px;
border-radius: 40px;
margin: 15px 20px 15px 10px;
@ -356,17 +368,29 @@ h2 {
text-decoration: none;
color: #fff;
font-weight: 700;
border: none;
cursor: pointer;
}
.nav-cta > .button:hover {
.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;
@ -717,4 +741,4 @@ fieldset p {
#theme-editor-form button[type="button"]#choose-font-btn {
margin-top: 16px;
}
}

View File

@ -7,19 +7,13 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
</head>
<body>
<!-- Top bar -->
<!-- Top bar -->
<div class="nav-bar">
<div class="content-inner nav">
<div class="nav-cta">
<div class="arrow"></div>
<a class="button" href="/site-info">
<span id="step">← Back to Site Info</span>
</a>
</div>
<input type="checkbox" id="nav-check">
<div class="nav-header">
<div class="nav-title">
<img src="../img/logo.svg">
<img src="{{ url_for('static', filename='img/logo.svg') }}">
</div>
</div>
<div class="nav-btn">
@ -31,9 +25,12 @@
</div>
<div class="nav-links">
<ul class="nav-list">
<li class="nav-item appear2"><a href="/site-info">Site info</a></li>
<li class="nav-item appear2"><a href="/theme-editor">Theme editor</a></li>
<li class="nav-item appear2"><a href="#">Gallery</a></li>
<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>
@ -197,6 +194,17 @@
</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">&times;</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>
<script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
</body>
</html>