card-generator/static/app.js

197 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Card Generator — frontend */
const $ = (id) => document.getElementById(id);
const preview = $("preview-frame");
const status = $("status");
let state = {
eyebrow: "RÉALISATION · SYMFONY",
title: "pokedwwmm",
title_2: "",
tagline: "151 Pokémon · Twig Components · JSON",
accent: "#ef4444",
logo_color:"#ef4444",
logo_kind: "preset", // preset | upload | text | none
logo_value:"symfony",
logo_size: 260,
};
let renderTimer = null;
let logoLibrary = [];
/* ── Wire up form ──────────────────────────────────────────────── */
const inputBindings = {
eyebrow: "eyebrow",
title: "title",
title_2: "title_2",
tagline: "tagline",
accent: "accent",
logo_color: "logo_color",
};
for (const [stateKey, inputId] of Object.entries(inputBindings)) {
$(inputId).addEventListener("input", (e) => {
state[stateKey] = e.target.value;
schedule();
});
}
$("logo_size").addEventListener("input", (e) => {
state.logo_size = +e.target.value;
$("logo_size_val").value = `${state.logo_size}px`;
schedule();
});
/* Swatches */
document.querySelectorAll(".swatch").forEach(btn => {
const c = btn.dataset.color;
btn.style.background = c;
btn.addEventListener("click", () => {
state.accent = c;
state.logo_color = c;
$("accent").value = c;
$("logo_color").value = c;
schedule();
});
});
/* Segmented control: logo source */
document.querySelectorAll(".seg-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".seg-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
state.logo_kind = btn.dataset.kind;
document.querySelectorAll(".logo-source").forEach(el => { el.hidden = true; });
if (state.logo_kind === "preset") document.querySelector(".preset-source").hidden = false;
if (state.logo_kind === "upload") document.querySelector(".upload-source").hidden = false;
if (state.logo_kind === "text") document.querySelector(".text-source").hidden = false;
schedule();
});
});
/* Text logo input */
$("logo_text").addEventListener("input", (e) => {
if (state.logo_kind === "text") state.logo_value = e.target.value;
schedule();
});
/* Upload */
$("logo_upload").addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
const dataUrl = await new Promise((res, rej) => {
const r = new FileReader();
r.onload = () => res(r.result);
r.onerror = rej;
r.readAsDataURL(file);
});
state.logo_kind = "upload";
state.logo_value = dataUrl;
const preview = $("upload_preview");
preview.hidden = false;
preview.innerHTML = `<img src="${dataUrl}" alt="">`;
schedule();
});
/* ── Logo library ──────────────────────────────────────────────── */
async function loadLogoLibrary() {
const res = await fetch("/api/logos");
logoLibrary = await res.json();
const grid = $("logo_grid");
grid.innerHTML = "";
for (const slug of logoLibrary) {
const tile = document.createElement("div");
tile.className = "logo-tile";
tile.title = slug;
if (slug === state.logo_value) tile.classList.add("selected");
tile.dataset.slug = slug;
const svgRes = await fetch(`/logos/${slug}.svg`);
tile.innerHTML = await svgRes.text();
tile.addEventListener("click", () => {
grid.querySelectorAll(".logo-tile").forEach(t => t.classList.remove("selected"));
tile.classList.add("selected");
state.logo_kind = "preset";
state.logo_value = slug;
// Also update segmented control
document.querySelectorAll(".seg-btn").forEach(b =>
b.classList.toggle("active", b.dataset.kind === "preset"));
document.querySelectorAll(".logo-source").forEach(el => { el.hidden = true; });
document.querySelector(".preset-source").hidden = false;
schedule();
});
grid.appendChild(tile);
}
}
/* ── Render pipeline ───────────────────────────────────────────── */
function schedule() {
if (renderTimer) clearTimeout(renderTimer);
status.textContent = "rendu…"; status.className = "status working";
renderTimer = setTimeout(render, 120);
}
async function render() {
try {
const res = await fetch("/api/svg", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const svgText = await res.text();
preview.innerHTML = svgText;
status.textContent = "à jour"; status.className = "status ok";
} catch (e) {
console.error(e);
status.textContent = "erreur"; status.className = "status err";
}
}
/* ── Download buttons ──────────────────────────────────────────── */
$("btn-svg").addEventListener("click", async () => {
status.textContent = "génération SVG…"; status.className = "status working";
const res = await fetch("/api/svg", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
});
const blob = await res.blob();
downloadBlob(blob, `${$("filename").value || "card"}.svg`);
status.textContent = "SVG téléchargé"; status.className = "status ok";
});
$("btn-png").addEventListener("click", async () => {
status.textContent = "rendu PNG (rsvg-convert)…"; status.className = "status working";
const width = +$("export_width").value;
const height = Math.round(width / 1.6);
try {
const res = await fetch("/api/png", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...state, export_width: width, export_height: height,
filename: $("filename").value || "card" }),
});
if (!res.ok) throw new Error(await res.text());
const blob = await res.blob();
downloadBlob(blob, `${$("filename").value || "card"}.png`);
status.textContent = `PNG ${width}×${height} téléchargé`; status.className = "status ok";
} catch (e) {
console.error(e);
status.textContent = "erreur PNG"; status.className = "status err";
}
});
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url; a.download = filename;
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
}
/* ── Init ──────────────────────────────────────────────────────── */
loadLogoLibrary().then(render);