From a2aabbf8a99d2f219152f1da7c2cbd2ad37676ec Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Thu, 24 Sep 2026 15:01:58 +0200 Subject: [PATCH] Add custom checklist card --- custom_components/cookcli/www/cookcli-card.js | 143 ++++++++++++++++++ .../cookcli/www/cookcli-recipe-strategy.js | 16 +- 2 files changed, 153 insertions(+), 6 deletions(-) diff --git a/custom_components/cookcli/www/cookcli-card.js b/custom_components/cookcli/www/cookcli-card.js index a954aa4..19ffea3 100644 --- a/custom_components/cookcli/www/cookcli-card.js +++ b/custom_components/cookcli/www/cookcli-card.js @@ -636,8 +636,151 @@ class CookCliCardEditor extends HTMLElement { } } +/** + * Checklist d'ingrédients d'une étape (cases à cocher, état local). + * + * L'état coché est stocké dans localStorage du navigateur, par recette et par + * étape (`storage_key`) : il n'est pas partagé entre appareils, ni lié à la + * todo du Résumé. Config : + * type: custom:cookcli-checklist-card + * storage_key: chocolat-chaud.cook:2 + * items: [{ quantity: "200 ml", name: "lait" }, ...] + */ +class CookCliChecklistCard extends HTMLElement { + setConfig(config) { + if (!config || !Array.isArray(config.items)) { + throw new Error("La carte a besoin d'une liste `items`."); + } + this._config = config; + this._checked = this._load(); + + if (!this.shadowRoot) { + this.attachShadow({ mode: "open" }); + this.shadowRoot.addEventListener("change", (ev) => this._onChange(ev)); + this.shadowRoot.addEventListener("click", (ev) => this._onClick(ev)); + } + this._render(); + } + + getCardSize() { + return (this._config?.items?.length || 0) + 1; + } + + _storageKey() { + return `cookcli-checklist:${this._config.storage_key || "default"}`; + } + + // Identifiant d'une ligne : index + libellé, pour rester correct si deux + // lignes ont le même nom ou si la recette est modifiée. + _itemId(item, index) { + return `${index}:${item.quantity || ""} ${item.name || ""}`.trim(); + } + + _load() { + try { + const raw = localStorage.getItem(this._storageKey()); + const parsed = raw ? JSON.parse(raw) : []; + return new Set(Array.isArray(parsed) ? parsed : []); + } catch (err) { + return new Set(); + } + } + + _save() { + try { + localStorage.setItem(this._storageKey(), JSON.stringify([...this._checked])); + } catch (err) { + // localStorage indisponible : l'état reste valable jusqu'au prochain rendu. + } + } + + _onChange(ev) { + const input = ev.target.closest("input[data-id]"); + if (!input) return; + if (input.checked) this._checked.add(input.dataset.id); + else this._checked.delete(input.dataset.id); + this._save(); + this._render(); + } + + _onClick(ev) { + if (ev.target.closest("[data-reset]")) { + this._checked.clear(); + this._save(); + this._render(); + } + } + + _escape(str) { + const div = document.createElement("div"); + div.textContent = str ?? ""; + return div.innerHTML; + } + + _render() { + if (!this.shadowRoot) return; + const items = this._config.items; + const anyChecked = items.some((item, i) => this._checked.has(this._itemId(item, i))); + + const rows = items + .map((item, i) => { + const id = this._itemId(item, i); + const checked = this._checked.has(id); + return ` + `; + }) + .join(""); + + this.shadowRoot.innerHTML = ` + + + ${rows} + + + `; + } +} + customElements.define("cookcli-card", CookCliCard); customElements.define("cookcli-card-editor", CookCliCardEditor); +customElements.define("cookcli-checklist-card", CookCliChecklistCard); window.customCards = window.customCards || []; window.customCards.push({ diff --git a/custom_components/cookcli/www/cookcli-recipe-strategy.js b/custom_components/cookcli/www/cookcli-recipe-strategy.js index 8c8feb0..cff2d40 100644 --- a/custom_components/cookcli/www/cookcli-recipe-strategy.js +++ b/custom_components/cookcli/www/cookcli-recipe-strategy.js @@ -388,13 +388,17 @@ class CookCliRecipeViewStrategy extends HTMLElement { const leftCards = []; const stepIngredients = (step.items || []).filter((item) => item.type === "ingredient"); if (stepIngredients.length) { - const lines = stepIngredients.map((item) => { - const qty = item.quantity ?? "" - ? `${item.quantity.value ?? ""} ${item.quantity.unit ?? ""}`.trim() - : ""; - return `- ${qty ? `**${qty}** ` : ""}${item.name ?? ""}`; + leftCards.push({ + type: "custom:cookcli-checklist-card", + // recette + étape : l'état coché est propre à chaque étape de chaque recette + storage_key: `${config.path}:${tabIndex}`, + items: stepIngredients.map((item) => ({ + quantity: item.quantity + ? `${item.quantity.value ?? ""} ${item.quantity.unit ?? ""}`.trim() + : "", + name: item.name ?? "", + })), }); - leftCards.push({ type: "markdown", content: lines.join("\n") }); } if (config.timer_entity) {