/* global Module, MM, NCCFormatDuration */ Module.register("MMM-NextcloudCookbook", { defaults: { controlPath: "/MMM-NextcloudCookbook/control", controlUrl: "", animationSpeed: 400, shareUrl: "", sharePassword: "" }, start() { this.state = { active: false, recipe: null, error: null }; this.hiddenModules = false; window.addEventListener("resize", () => this.scheduleRecipeFit()); this.sendSocketNotification("CONFIG", this.config); }, getStyles() { return ["MMM-NextcloudCookbook.css"]; }, getScripts() { return [this.file("lib/format-duration.js")]; }, socketNotificationReceived(notification, payload) { if (notification !== "STATE") return; this.state = payload; this.setRecipeMode(Boolean(payload.active)); this.updateDom(this.config.animationSpeed); }, notificationReceived(notification) { if (notification === "MODULE_DOM_UPDATED") this.scheduleRecipeFit(); }, scheduleRecipeFit() { window.requestAnimationFrame(() => this.fitRecipe()); }, fitRecipe() { const root = document.getElementById(this.identifier)?.querySelector(".ncc-active"); const recipe = root?.querySelector(".ncc-recipe"); if (!root || !recipe) return; const style = window.getComputedStyle(root); const availableHeight = root.clientHeight - Number.parseFloat(style.paddingTop) - Number.parseFloat(style.paddingBottom); const availableWidth = root.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight); let minimum = 0.45; let maximum = 2; let best = minimum; for (let attempt = 0; attempt < 12; attempt++) { const scale = (minimum + maximum) / 2; root.style.setProperty("--ncc-type-scale", scale); const fits = recipe.scrollHeight <= availableHeight && recipe.scrollWidth <= availableWidth; if (fits) { best = scale; minimum = scale; } else { maximum = scale; } } root.style.setProperty("--ncc-type-scale", best.toFixed(3)); }, setRecipeMode(active) { if (active && !this.hiddenModules) { MM.getModules().exceptModule(this).enumerate((module) => { module.hide(this.config.animationSpeed, { lockString: this.name }); }); this.hiddenModules = true; } else if (!active && this.hiddenModules) { MM.getModules().exceptModule(this).enumerate((module) => { module.show(this.config.animationSpeed, { lockString: this.name }); }); this.hiddenModules = false; } }, getDom() { const root = document.createElement("section"); root.className = this.state.active ? "ncc-root ncc-active" : "ncc-root ncc-idle"; const qr = document.createElement("a"); qr.className = "ncc-qr"; qr.href = this.config.controlUrl || this.config.controlPath; qr.setAttribute("aria-label", "Open recipe controller"); const qrLabel = document.createElement("span"); qrLabel.textContent = "Recipe mode control:"; const qrImage = document.createElement("img"); qrImage.src = "/MMM-NextcloudCookbook/qr.svg"; qrImage.alt = "Recipe controller QR code"; qr.append(qrLabel, qrImage); root.appendChild(qr); if (!this.state.active) return root; if (this.state.error) { const error = document.createElement("p"); error.className = "ncc-error"; error.textContent = this.state.error; root.appendChild(error); return root; } if (!this.state.recipe) { const loading = document.createElement("p"); loading.className = "ncc-loading"; loading.textContent = "Loading recipe…"; root.appendChild(loading); return root; } root.appendChild(this.renderRecipe(this.state.recipe)); return root; }, renderRecipe(recipe) { const article = document.createElement("article"); article.className = "ncc-recipe"; const header = document.createElement("header"); const title = document.createElement("h1"); title.textContent = recipe.name || "Untitled recipe"; header.appendChild(title); const metadata = [ ["Yield", recipe.recipeYield], ["Prep", NCCFormatDuration(recipe.prepTime)], ["Cook", NCCFormatDuration(recipe.cookTime)], ["Total", NCCFormatDuration(recipe.totalTime)] ].filter(([, value]) => value); if (metadata.length) { const list = document.createElement("dl"); list.className = "ncc-meta"; metadata.forEach(([label, value]) => { const term = document.createElement("dt"); term.textContent = label; const detail = document.createElement("dd"); detail.textContent = value; list.append(term, detail); }); header.appendChild(list); } article.appendChild(header); const columns = document.createElement("div"); columns.className = "ncc-columns"; const ingredients = document.createElement("section"); const ingredientsTitle = document.createElement("h2"); ingredientsTitle.textContent = "Ingredients"; ingredients.appendChild(ingredientsTitle); const ingredientList = document.createElement("ul"); (recipe.recipeIngredient || []).forEach((ingredient) => { const item = document.createElement("li"); item.textContent = ingredient; ingredientList.appendChild(item); }); ingredients.appendChild(ingredientList); const instructions = document.createElement("section"); const instructionsTitle = document.createElement("h2"); instructionsTitle.textContent = "Instructions"; instructions.appendChild(instructionsTitle); const instructionList = document.createElement("ol"); this.flattenInstructions(recipe.recipeInstructions || []).forEach((step) => { const item = document.createElement("li"); item.textContent = step; instructionList.appendChild(item); }); instructions.appendChild(instructionList); columns.append(ingredients, instructions); article.appendChild(columns); return article; }, flattenInstructions(instructions) { return instructions.flatMap((instruction) => { if (typeof instruction === "string") return [instruction]; if (Array.isArray(instruction.itemListElement)) { return this.flattenInstructions(instruction.itemListElement); } return instruction.text ? [instruction.text] : []; }); } });