202 lines
6.6 KiB
JavaScript
202 lines
6.6 KiB
JavaScript
/* global Module, MM, NCCFormatDuration */
|
|
|
|
Module.register("MMM-NextcloudCookbook", {
|
|
defaults: {
|
|
controlPath: "/MMM-NextcloudCookbook/control",
|
|
controlUrl: "",
|
|
animationSpeed: 400,
|
|
layout: "stacked",
|
|
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");
|
|
const qrParameters = new URLSearchParams();
|
|
if (this.config.controlUrl) qrParameters.set("url", this.config.controlUrl);
|
|
qrImage.src = `/MMM-NextcloudCookbook/qr.svg${qrParameters.size ? `?${qrParameters}` : ""}`;
|
|
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");
|
|
const layout = this.config.layout === "side-by-side" ? "side-by-side" : "stacked";
|
|
columns.className = `ncc-columns ncc-layout-${layout}`;
|
|
|
|
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] : [];
|
|
});
|
|
}
|
|
});
|