Files
2025-09-16 22:20:21 +02:00

291 lines
9.6 KiB
JavaScript
Raw Permalink 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.

// ==============================
// 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 = `
<img class="cui-thumb" src="${r.image || ""}" alt="">
<div class="cui-info">
<div class="cui-name" title="${r.name}">${r.name}</div>
<div class="cui-meta">${r.tempsDePreparation ? (r.tempsDePreparation + " min") : ""}</div>
</div>
`;
// 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 lUA)
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();
});
})();