Build system with zip download
This commit is contained in:
		
							
								
								
									
										81
									
								
								src/webui/js/build.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/webui/js/build.js
									
									
									
									
									
										Normal 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";
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@@ -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),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user