Ajout de HelloFresh
This commit is contained in:
288
wwwroot/js/HelloFresh/cuisine.js
Normal file
288
wwwroot/js/HelloFresh/cuisine.js
Normal file
@@ -0,0 +1,288 @@
|
||||
// ==============================
|
||||
// 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 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();
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user