// ============================== // 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(); }); })();