const foodSearch = document.querySelector("#food-search"); const searchEntries = document.querySelector("#search-entries"); const foodMenu = document.querySelector("#food-menu"); const menuTable = document.querySelector("#menu-table"); const summaryEnergy = document.querySelector("#summary-energy"); const summaryFat = document.querySelector("#summary-fat"); const summaryCarbs = document.querySelector("#summary-carbs"); const summaryProteins = document.querySelector("#summary-proteins"); updateSummary(); initFoodList(); foodSearch.addEventListener("input", onFoodSearched); if (foodSearch.value != "") onFoodSearched(); function initFoodList() { const dataList = document.querySelector("#food-list"); for (let food of foods) { const option = document.createElement("option"); option.setAttribute("value", food.name); dataList.appendChild(option); } } function onFoodSearched() { const food = foodSearch.value; const foodLC = food.toLowerCase(); if (food == "") { createSearchEntries([]); return; } const foodsCopy = structuredClone(foods); foodsCopy.sort((a, b) => { const aNameLC = a.name.toLowerCase(); const bNameLC = b.name.toLowerCase(); const ainc = aNameLC.includes(foodLC); const binc = bNameLC.includes(foodLC); if (ainc && binc) { return 0; } else if (ainc) { return -1; } else if (binc) { return 1; } const distA = levenshtein(foodLC, aNameLC); const distB = levenshtein(foodLC, bNameLC); return distA - distB; }) const maxDist = 10; let maxAmount = 10; let amount = 0; for (let f of foodsCopy) { const fnameLC = f.name.toLowerCase(); if (fnameLC.includes(foodLC)) { amount++; maxAmount++; continue; } const dist = levenshtein(foodLC, fnameLC); if (dist >= maxDist) break; amount++; } createSearchEntries(foodsCopy.slice(0, Math.min(amount, maxAmount, 30))); } async function createSearchEntries(foods) { const nodes = []; for (let food of foods) { const node = await JST.load("search-entry"); node.querySelector(".search-entry-name").textContent = food.name; node.querySelector(".search-entry").addEventListener("click", () => addMenuItem(food)); nodes.push(node); } searchEntries.replaceChildren(...nodes); } async function addMenuItem(food) { const alreadyAddedMenuItem = document.querySelector(`#food-menu .menu-item[data-food-name="${food.name}"]`); if (alreadyAddedMenuItem !== null) { alreadyAddedMenuItem.style.animation = "highlight 0.25s linear"; setTimeout(() => alreadyAddedMenuItem.style.animation = "", 250); return; } const node = await JST.load("menu-item"); const nodeRoot = node.querySelector(".menu-item"); const itemName = node.querySelector(".menu-item-name"); const itemKcal = node.querySelector(".menu-item-kcal"); const itemUnit = node.querySelector(".menu-item-unit"); const itemAmount = node.querySelector(".menu-item-amount"); const kcalAmount = node.querySelector(".menu-item-energy"); const fatAmount = node.querySelector(".menu-item-fat"); const carbsAmount = node.querySelector(".menu-item-carbs"); const proteinsAmount = node.querySelector(".menu-item-proteins"); const score = node.querySelector(".menu-item-score"); nodeRoot.setAttribute("data-food-name", food.name); itemName.textContent = food.name; itemKcal.textContent = `${food.kcal} kcal/100${food.unit}`; itemUnit.textContent = food.unit; fatAmount.textContent = `${food.fat}g`; carbsAmount.textContent = `${food.carbs}g`; proteinsAmount.textContent = `${food.proteins}g`; score.textContent = food.density.toFixed(1); score.classList.add(getScoreColor(food.density, food.unit)); node.querySelector(".delete-menu-item").addEventListener("click", () => { foodMenu.removeChild(nodeRoot); updateSummary(); }); const updateKcalAmount = () => { if (!/^[0-9]+$/.test(itemAmount.value)) { return; } const amount = parseInt(itemAmount.value); kcalAmount.textContent = `${parseInt(Math.round(food.kcal / 100 * amount))}kcal`; fatAmount.textContent = `${parseInt(Math.round(food.fat / 100 * amount))}g`; carbsAmount.textContent = `${parseInt(Math.round(food.carbs / 100 * amount))}g`; proteinsAmount.textContent = `${parseInt(Math.round(food.proteins / 100 * amount))}g`; updateSummary(); }; const evaluateKcalAmount = () => { if (/^[0-9]+$/.test(itemAmount.value)) { return; } const evaluator = new Worker("evaluator.js"); evaluator.postMessage(itemAmount.value); evaluator.onmessage = e => { const amount = Math.max(parseInt(e.data), 0); itemAmount.value = amount; kcalAmount.textContent = `${parseInt(Math.round(food.kcal / 100 * amount))}kcal`; fatAmount.textContent = `${parseInt(Math.round(food.fat / 100 * amount))}g`; carbsAmount.textContent = `${parseInt(Math.round(food.carbs / 100 * amount))}g`; proteinsAmount.textContent = `${parseInt(Math.round(food.proteins / 100 * amount))}g`; updateSummary(); }; } itemAmount.value = food.avg_amount ?? 100; itemAmount.addEventListener("input", updateKcalAmount); itemAmount.addEventListener("change", evaluateKcalAmount); updateKcalAmount(); foodMenu.appendChild(node); updateSummary(); } function updateSummary() { const menuItems = foodMenu.querySelectorAll(".menu-item"); menuTable.style.display = menuItems.length > 0 ? "table" : "none"; let energy = 0; let fat = 0; let carbs = 0; let proteins = 0; for (let menuItem of menuItems) { energy += parseInt(menuItem.querySelector(".menu-item-energy").textContent); fat += parseInt(menuItem.querySelector(".menu-item-fat").textContent); carbs += parseInt(menuItem.querySelector(".menu-item-carbs").textContent); proteins += parseInt(menuItem.querySelector(".menu-item-proteins").textContent); } summaryEnergy.textContent = `${energy}kcal`; summaryFat.textContent = `${fat}g`; summaryCarbs.textContent = `${carbs}g`; summaryProteins.textContent = `${proteins}g`; } function getScoreColor(density, unit) { switch (unit) { case "g": { if (density >= 2.4) { return "red" } else if (density >= 1.7) { return "orange" } else { return "green"; } } case "ml": { if (density >= 0.3) { return "red" } else { return "green"; } } } }