// ==============================
// Cuisine / Lecteur de recettes
// ==============================
(() => {
"use strict";
// ---------- Sélecteurs ----------
const $ = (sel) => document.querySelector(sel);
const els = {
root: $("#cuisineApp"),
scroller: $("#recipeScroller"),
title: $("#currentName"),
btnOpen: $("#openPdf"),
btnToggle: $("#toggleSide"),
iframePrev: $("#pdfPreview"),
placeholder: $("#pdfPlaceholder"),
overlay: $("#viewerOverlay"),
overlayTit: $("#viewerTitle"),
iframeFull: $("#pdfFrameFull"),
btnClose: $("#closeViewer"),
};
// ---------- Etat ----------
const PDF_DEFAULT_ZOOM = "page-fit"; // "page-fit" | "page-width" | "100"
const LS_KEY_COLLAPSED = "cui_sidebar_collapsed";
/** @type {{id:string,name:string,image:string,tempsDePreparation:number|null,pdf:string,portions:number}[]} */
let RECIPES = [];
let CUR = -1; // index courant
// ---------- Utils ----------
const normalize = (s) => (s ?? "").toString().trim().toLowerCase();
const proxify = (url) => url ? `/HelloFresh/ProxyPdf?url=${encodeURIComponent(url)}` : "";
function buildPdfSrc(rawUrl) {
if (!rawUrl) return "";
const base = `/HelloFresh/ProxyPdf?url=${encodeURIComponent(rawUrl)}`;
// cache la barre (toolbar=0) + fixe le zoom
return `${base}#toolbar=0&zoom=${encodeURIComponent(PDF_DEFAULT_ZOOM)}`;
}
// Dédup par nom (on garde la 1ère qui a un pdf / image)
function dedupByName(rows) {
const map = new Map(); // key: normalized name -> row
for (const r of rows) {
const key = normalize(r.name);
if (!key) continue;
if (!map.has(key)) {
map.set(key, r);
} else {
const kept = map.get(key);
// privilégier une entrée avec PDF / image
if (!kept.pdf && r.pdf) map.set(key, r);
else if (!kept.image && r.image) map.set(key, r);
}
}
return [...map.values()];
}
// ---------- Rendu de la liste ----------
function renderList(list) {
const wrap = els.scroller;
if (!wrap) return;
wrap.innerHTML = "";
list.forEach((r, idx) => {
const card = document.createElement("div");
card.className = "cui-card";
card.tabIndex = 0;
card.dataset.idx = String(idx);
card.innerHTML = `
${r.name}
${r.tempsDePreparation ? (r.tempsDePreparation + " min") : ""}
`;
// clic / entrée -> sélection
card.addEventListener("click", () => selectIdx(idx));
card.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
selectIdx(idx);
}
});
wrap.appendChild(card);
});
markActive();
}
function markActive() {
els.scroller?.querySelectorAll(".cui-card").forEach((el, i) => {
el.classList.toggle("active", i === CUR);
});
}
// ---------- Sélection / Toolbar / Preview ----------
function selectIdx(idx) {
if (idx < 0 || idx >= RECIPES.length) return;
CUR = idx;
const r = RECIPES[CUR];
// Titre (ellipsis géré par CSS)
if (els.title) els.title.textContent = r.name || "Recette";
// Bouton ouvrir
if (els.btnOpen) {
const can = !!r.pdf;
els.btnOpen.disabled = !can;
els.btnOpen.setAttribute("aria-disabled", String(!can));
els.btnOpen.title = can ? "Ouvrir le PDF" : "PDF indisponible";
}
// Aperçu PDF
loadPreview(r.pdf);
// Etat actif visuel
markActive();
}
function loadPreview(url) {
if (!els.iframePrev || !els.placeholder) return;
if (url) {
els.iframePrev.src = buildPdfSrc(url);
els.iframePrev.style.display = "block";
els.placeholder.style.display = "none";
} else {
els.iframePrev.removeAttribute("src");
els.iframePrev.style.display = "none";
els.placeholder.style.display = "grid";
}
}
// ---------- Plein écran ----------
function openViewer() {
if (CUR < 0) return;
const r = RECIPES[CUR];
if (!r.pdf || !els.overlay || !els.iframeFull || !els.overlayTit) return;
els.overlayTit.textContent = r.name || "PDF";
els.iframeFull.src = buildPdfSrc(r.pdf);
els.overlay.classList.remove("hidden");
els.overlay.setAttribute("aria-hidden", "false");
// tente le plein écran (si autorisé par l’UA)
try { els.overlay.requestFullscreen(); } catch { }
}
function closeViewer() {
if (!els.overlay || !els.iframeFull) return;
els.iframeFull.removeAttribute("src");
els.overlay.classList.add("hidden");
els.overlay.setAttribute("aria-hidden", "true");
if (document.fullscreenElement) {
try { document.exitFullscreen(); } catch { }
}
}
// Navigation dans le viewer
function next() {
if (CUR < RECIPES.length - 1) {
selectIdx(CUR + 1);
if (!els.overlay?.classList.contains("hidden") && els.iframeFull) {
const r = RECIPES[CUR];
els.iframeFull.src = buildPdfSrc(r.pdf || "");
}
}
}
function prev() {
if (CUR > 0) {
selectIdx(CUR - 1);
if (!els.overlay?.classList.contains("hidden") && els.iframeFull) {
const r = RECIPES[CUR];
els.iframeFull.src = buildPdfSrc(r.pdf || "");
}
}
}
// ---------- Données ----------
async function fetchRecipes() {
const res = await fetch("/HelloFresh/GetRecipesOwned", { credentials: "same-origin" });
if (!res.ok) throw new Error("HTTP " + res.status);
const raw = await res.json();
// Normalisation des propriétés
const norm = (r) => ({
id: r.id ?? r.Id ?? "",
name: r.name ?? r.Name ?? "",
image: r.image ?? r.Image ?? "",
tempsDePreparation: r.tempsDePreparation ?? r.TempsDePreparation ?? null,
pdf: r.pdf ?? r.Pdf ?? "",
portions: Number(r.portions ?? r.Portions ?? 0) || 0
});
let rows = Array.isArray(raw) ? raw.map(norm) : [];
rows = dedupByName(rows);
// Tri par nom (français)
rows.sort((a, b) => String(a.name).localeCompare(String(b.name), "fr", { sensitivity: "base" }));
return rows;
}
async function loadRecipes() {
try {
RECIPES = await fetchRecipes();
renderList(RECIPES);
// Auto-sélection : d'abord la 1ère avec PDF, sinon la 1ère tout court
const firstPdf = RECIPES.findIndex(r => !!r.pdf);
const idx = firstPdf >= 0 ? firstPdf : (RECIPES.length ? 0 : -1);
if (idx >= 0) selectIdx(idx);
else selectIdx(-1);
} catch (err) {
console.error("[Cuisine] GetRecipesOwned failed:", err);
RECIPES = [];
renderList([]);
selectIdx(-1);
}
}
// ---------- Sidebar collapse (persisté) ----------
function setCollapsed(collapsed) {
if (!els.root) return;
els.root.classList.toggle("collapsed", !!collapsed);
els.btnToggle?.setAttribute("aria-expanded", String(!collapsed));
try { localStorage.setItem(LS_KEY_COLLAPSED, JSON.stringify(!!collapsed)); } catch { }
}
function getCollapsed() {
try {
const v = localStorage.getItem(LS_KEY_COLLAPSED);
return v ? JSON.parse(v) : false;
} catch { return false; }
}
// ---------- Gestes / Clavier (viewer) ----------
(function wireGestures() {
if (!els.overlay) return;
// Swipe horizontal (tablette)
let touchStartX = 0;
const SWIPE_MIN = 60;
els.overlay.addEventListener("touchstart", (e) => {
if (!e.changedTouches?.length) return;
touchStartX = e.changedTouches[0].clientX;
}, { passive: true });
els.overlay.addEventListener("touchend", (e) => {
if (!e.changedTouches?.length) return;
const dx = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(dx) > SWIPE_MIN) (dx < 0 ? next : prev)();
}, { passive: true });
// Clavier (visible uniquement dans le viewer)
document.addEventListener("keydown", (e) => {
if (els.overlay?.classList.contains("hidden")) return;
if (e.key === "ArrowRight") next();
else if (e.key === "ArrowLeft") prev();
else if (e.key === "Escape") closeViewer();
});
})();
// ---------- Wiring des boutons ----------
function wireUI() {
els.btnOpen?.addEventListener("click", openViewer);
els.btnClose?.addEventListener("click", closeViewer);
els.btnToggle?.addEventListener("click", () => {
setCollapsed(!els.root?.classList.contains("collapsed"));
});
}
// ---------- Boot ----------
document.addEventListener("DOMContentLoaded", async () => {
// Appliquer l’état collapsé mémorisé
setCollapsed(getCollapsed());
wireUI();
await loadRecipes();
});
})();