diff --git a/iframe.html b/iframe.html index 877ee92..22bb23a 100644 --- a/iframe.html +++ b/iframe.html @@ -2114,15 +2114,19 @@ const CALCULATOR_CONFIG = { // Kayafied reference source tracking this.kibbleRefId = null; this.gcRefId = null; + this.storageKey = 'kaya_calculator_state_v1'; this.init(); } init() { this.applyTheme(); this.applyScale(); - this.initializeFoodSources(); - this.bindEvents(); this.updateUnitLabels(); + const restored = this.loadStateFromStorage(); + if (!restored) { + this.initializeFoodSources(); + } + this.bindEvents(); this.setupIframeResize(); // Show the calculator with fade-in @@ -2130,6 +2134,117 @@ const CALCULATOR_CONFIG = { container.classList.add('loaded'); } + // Persistence helpers + saveStateToStorage() { + try { + const ageInput = document.getElementById('ageMonths'); + const unitSelect = document.getElementById('unit'); + const daysInput = document.getElementById('days'); + const state = { + version: 1, + age: ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null, + unit: unitSelect ? unitSelect.value : 'g', + days: daysInput && daysInput.value ? parseInt(daysInput.value) : 1, + showPerMeal: !!this.showPerMeal, + mealsPerDay: this.mealsPerDay, + foodSources: this.foodSources.map(fs => ({ + id: fs.id, + name: fs.name, + energy: fs.energy, + energyUnit: fs.energyUnit, + percentage: fs.percentage, + isLocked: fs.isLocked, + chartType: fs.chartType || null, + splitByMeals: (fs.splitByMeals === undefined ? true : fs.splitByMeals) + })) + }; + localStorage.setItem(this.storageKey, JSON.stringify(state)); + } catch (e) { + // Ignore storage errors (private mode, etc.) + } + } + + loadStateFromStorage() { + try { + const raw = localStorage.getItem(this.storageKey); + if (!raw) return false; + const state = JSON.parse(raw); + if (!state || typeof state !== 'object') return false; + + // Restore unit first + const unitSelect = document.getElementById('unit'); + if (unitSelect && state.unit && (state.unit === 'g' || state.unit === 'kg')) { + unitSelect.value = state.unit; + this.setActiveUnitButton(state.unit); + } + + // Restore days + const daysInput = document.getElementById('days'); + if (daysInput && state.days) { + daysInput.value = state.days; + this.updateDayLabel(); + } + + // Restore meal settings + this.showPerMeal = !!state.showPerMeal; + this.mealsPerDay = state.mealsPerDay || 2; + const showDaily = document.getElementById('showDaily'); + const showPerMeal = document.getElementById('showPerMeal'); + const mealsPerDayInput = document.getElementById('mealsPerDay'); + const mealInputGroup = document.getElementById('mealInputGroup'); + if (showDaily && showPerMeal) { + if (this.showPerMeal) { + showPerMeal.checked = true; + if (mealInputGroup) mealInputGroup.style.display = 'inline-flex'; + } else { + showDaily.checked = true; + if (mealInputGroup) mealInputGroup.style.display = 'none'; + } + } + if (mealsPerDayInput) { + mealsPerDayInput.value = this.mealsPerDay; + } + + // Restore age + const ageInput = document.getElementById('ageMonths'); + if (ageInput && (state.age || state.age === 0)) { + ageInput.value = state.age; + } + + // Restore food sources + if (Array.isArray(state.foodSources) && state.foodSources.length) { + this.foodSources = []; + this.kibbleRefId = null; + this.gcRefId = null; + state.foodSources.forEach(saved => { + const fs = { + id: saved.id || this.generateFoodSourceId(), + name: saved.name || 'Food Source', + energy: saved.energy || '', + energyUnit: saved.energyUnit || 'kcal100g', + percentage: typeof saved.percentage === 'number' ? saved.percentage : 0, + isLocked: !!saved.isLocked, + chartType: saved.chartType || null, + splitByMeals: (saved.splitByMeals === undefined ? true : saved.splitByMeals) + }; + this.foodSources.push(fs); + this.renderFoodSource(fs); + if (fs.chartType === 'kibble' && !this.kibbleRefId) this.kibbleRefId = fs.id; + if (fs.chartType === 'gc' && !this.gcRefId) this.gcRefId = fs.id; + }); + this.updateAddButton(); + this.updateRemoveButtons(); + this.refreshAllPercentageUI(); + } + + // Trigger calculations + this.updateCalorieCalculations(); + return true; + } catch (e) { + return false; + } + } + getThemeFromURL() { const urlParams = new URLSearchParams(window.location.search); const theme = urlParams.get('theme'); @@ -2234,6 +2349,7 @@ const CALCULATOR_CONFIG = { this.updateAddButton(); this.updateRemoveButtons(); this.refreshAllPercentageUI(); + this.saveStateToStorage(); } removeFoodSource(id) { @@ -2262,6 +2378,7 @@ const CALCULATOR_CONFIG = { this.updateRemoveButtons(); this.refreshAllPercentageUI(); this.updateCalorieCalculations(); + this.saveStateToStorage(); } generateFoodSourceId() { @@ -2543,6 +2660,7 @@ const CALCULATOR_CONFIG = { } }); + this.saveStateToStorage(); return true; } @@ -2563,6 +2681,7 @@ const CALCULATOR_CONFIG = { // Update food calculations this.updateFoodCalculations(); + this.saveStateToStorage(); } updateSliderConstraints(foodSource) { @@ -2722,6 +2841,7 @@ const CALCULATOR_CONFIG = { const newName = nameInput.value.trim() || `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`; this.updateFoodSourceData(id, 'name', newName); this.updateFoodCalculations(); // This will refresh the food amount breakdown with new names + this.saveStateToStorage(); }); nameInput.addEventListener('blur', () => { @@ -2731,6 +2851,7 @@ const CALCULATOR_CONFIG = { nameInput.value = defaultName; this.updateFoodSourceData(id, 'name', defaultName); this.updateFoodCalculations(); + this.saveStateToStorage(); } }); } @@ -2755,6 +2876,7 @@ const CALCULATOR_CONFIG = { } } this.updateFoodCalculations(); + this.saveStateToStorage(); }); energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id)); } @@ -2776,32 +2898,37 @@ const CALCULATOR_CONFIG = { // Cups display not available; default to grams unitSelect.value = 'g'; this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcal100g': - // For kcal/100g, select grams - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcalkg': - // For kcal/kg, also select grams (or could be kg) - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcalcan': - // For kcal/can, use grams as default - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - } - } else { - // No unit select, just update calculations this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcal100g': + // For kcal/100g, select grams + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcalkg': + // For kcal/kg, also select grams (or could be kg) + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcalcan': + // For kcal/can, use grams as default + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; } - }); + } else { + // No unit select, just update calculations + this.updateFoodCalculations(); + this.saveStateToStorage(); + } + }); } if (percentageSlider) { @@ -2855,6 +2982,7 @@ const CALCULATOR_CONFIG = { this.updateLockIcon(id); this.updateLockStates(); this.refreshAllPercentageUI(); + this.saveStateToStorage(); } updateLockIcon(id) { @@ -2945,19 +3073,20 @@ const CALCULATOR_CONFIG = { // Kayafied: age input drives energy target if (ageInput) { - ageInput.addEventListener('input', () => this.updateCalorieCalculations()); - ageInput.addEventListener('blur', () => this.updateCalorieCalculations()); + ageInput.addEventListener('input', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); }); + ageInput.addEventListener('blur', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); }); } if (daysInput) { daysInput.addEventListener('input', () => { this.updateDayLabel(); this.updateFoodCalculations(); + this.saveStateToStorage(); }); daysInput.addEventListener('blur', () => this.validateDays()); } - if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations()); + if (unitSelect) unitSelect.addEventListener('change', () => { this.updateFoodCalculations(); this.saveStateToStorage(); }); // Unit button event listeners const unitButtons = document.querySelectorAll('.dog-calculator-unit-btn'); @@ -2969,6 +3098,7 @@ const CALCULATOR_CONFIG = { if (unitSelect) { unitSelect.value = selectedUnit; this.updateFoodCalculations(); + this.saveStateToStorage(); } }); }); diff --git a/src/js/calculator.js b/src/js/calculator.js index a7b52f2..36f5b51 100644 --- a/src/js/calculator.js +++ b/src/js/calculator.js @@ -18,15 +18,19 @@ // Kayafied reference source tracking this.kibbleRefId = null; this.gcRefId = null; + this.storageKey = 'kaya_calculator_state_v1'; this.init(); } init() { this.applyTheme(); this.applyScale(); - this.initializeFoodSources(); - this.bindEvents(); this.updateUnitLabels(); + const restored = this.loadStateFromStorage(); + if (!restored) { + this.initializeFoodSources(); + } + this.bindEvents(); this.setupIframeResize(); // Show the calculator with fade-in @@ -34,6 +38,117 @@ container.classList.add('loaded'); } + // Persistence helpers + saveStateToStorage() { + try { + const ageInput = document.getElementById('ageMonths'); + const unitSelect = document.getElementById('unit'); + const daysInput = document.getElementById('days'); + const state = { + version: 1, + age: ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null, + unit: unitSelect ? unitSelect.value : 'g', + days: daysInput && daysInput.value ? parseInt(daysInput.value) : 1, + showPerMeal: !!this.showPerMeal, + mealsPerDay: this.mealsPerDay, + foodSources: this.foodSources.map(fs => ({ + id: fs.id, + name: fs.name, + energy: fs.energy, + energyUnit: fs.energyUnit, + percentage: fs.percentage, + isLocked: fs.isLocked, + chartType: fs.chartType || null, + splitByMeals: (fs.splitByMeals === undefined ? true : fs.splitByMeals) + })) + }; + localStorage.setItem(this.storageKey, JSON.stringify(state)); + } catch (e) { + // Ignore storage errors (private mode, etc.) + } + } + + loadStateFromStorage() { + try { + const raw = localStorage.getItem(this.storageKey); + if (!raw) return false; + const state = JSON.parse(raw); + if (!state || typeof state !== 'object') return false; + + // Restore unit first + const unitSelect = document.getElementById('unit'); + if (unitSelect && state.unit && (state.unit === 'g' || state.unit === 'kg')) { + unitSelect.value = state.unit; + this.setActiveUnitButton(state.unit); + } + + // Restore days + const daysInput = document.getElementById('days'); + if (daysInput && state.days) { + daysInput.value = state.days; + this.updateDayLabel(); + } + + // Restore meal settings + this.showPerMeal = !!state.showPerMeal; + this.mealsPerDay = state.mealsPerDay || 2; + const showDaily = document.getElementById('showDaily'); + const showPerMeal = document.getElementById('showPerMeal'); + const mealsPerDayInput = document.getElementById('mealsPerDay'); + const mealInputGroup = document.getElementById('mealInputGroup'); + if (showDaily && showPerMeal) { + if (this.showPerMeal) { + showPerMeal.checked = true; + if (mealInputGroup) mealInputGroup.style.display = 'inline-flex'; + } else { + showDaily.checked = true; + if (mealInputGroup) mealInputGroup.style.display = 'none'; + } + } + if (mealsPerDayInput) { + mealsPerDayInput.value = this.mealsPerDay; + } + + // Restore age + const ageInput = document.getElementById('ageMonths'); + if (ageInput && (state.age || state.age === 0)) { + ageInput.value = state.age; + } + + // Restore food sources + if (Array.isArray(state.foodSources) && state.foodSources.length) { + this.foodSources = []; + this.kibbleRefId = null; + this.gcRefId = null; + state.foodSources.forEach(saved => { + const fs = { + id: saved.id || this.generateFoodSourceId(), + name: saved.name || 'Food Source', + energy: saved.energy || '', + energyUnit: saved.energyUnit || 'kcal100g', + percentage: typeof saved.percentage === 'number' ? saved.percentage : 0, + isLocked: !!saved.isLocked, + chartType: saved.chartType || null, + splitByMeals: (saved.splitByMeals === undefined ? true : saved.splitByMeals) + }; + this.foodSources.push(fs); + this.renderFoodSource(fs); + if (fs.chartType === 'kibble' && !this.kibbleRefId) this.kibbleRefId = fs.id; + if (fs.chartType === 'gc' && !this.gcRefId) this.gcRefId = fs.id; + }); + this.updateAddButton(); + this.updateRemoveButtons(); + this.refreshAllPercentageUI(); + } + + // Trigger calculations + this.updateCalorieCalculations(); + return true; + } catch (e) { + return false; + } + } + getThemeFromURL() { const urlParams = new URLSearchParams(window.location.search); const theme = urlParams.get('theme'); @@ -138,6 +253,7 @@ this.updateAddButton(); this.updateRemoveButtons(); this.refreshAllPercentageUI(); + this.saveStateToStorage(); } removeFoodSource(id) { @@ -166,6 +282,7 @@ this.updateRemoveButtons(); this.refreshAllPercentageUI(); this.updateCalorieCalculations(); + this.saveStateToStorage(); } generateFoodSourceId() { @@ -447,6 +564,7 @@ } }); + this.saveStateToStorage(); return true; } @@ -467,6 +585,7 @@ // Update food calculations this.updateFoodCalculations(); + this.saveStateToStorage(); } updateSliderConstraints(foodSource) { @@ -626,6 +745,7 @@ const newName = nameInput.value.trim() || `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`; this.updateFoodSourceData(id, 'name', newName); this.updateFoodCalculations(); // This will refresh the food amount breakdown with new names + this.saveStateToStorage(); }); nameInput.addEventListener('blur', () => { @@ -635,6 +755,7 @@ nameInput.value = defaultName; this.updateFoodSourceData(id, 'name', defaultName); this.updateFoodCalculations(); + this.saveStateToStorage(); } }); } @@ -659,6 +780,7 @@ } } this.updateFoodCalculations(); + this.saveStateToStorage(); }); energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id)); } @@ -680,32 +802,37 @@ // Cups display not available; default to grams unitSelect.value = 'g'; this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcal100g': - // For kcal/100g, select grams - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcalkg': - // For kcal/kg, also select grams (or could be kg) - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - case 'kcalcan': - // For kcal/can, use grams as default - unitSelect.value = 'g'; - this.setActiveUnitButton('g'); - this.updateFoodCalculations(); - break; - } - } else { - // No unit select, just update calculations this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcal100g': + // For kcal/100g, select grams + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcalkg': + // For kcal/kg, also select grams (or could be kg) + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; + case 'kcalcan': + // For kcal/can, use grams as default + unitSelect.value = 'g'; + this.setActiveUnitButton('g'); + this.updateFoodCalculations(); + this.saveStateToStorage(); + break; } - }); + } else { + // No unit select, just update calculations + this.updateFoodCalculations(); + this.saveStateToStorage(); + } + }); } if (percentageSlider) { @@ -759,6 +886,7 @@ this.updateLockIcon(id); this.updateLockStates(); this.refreshAllPercentageUI(); + this.saveStateToStorage(); } updateLockIcon(id) { @@ -849,19 +977,20 @@ // Kayafied: age input drives energy target if (ageInput) { - ageInput.addEventListener('input', () => this.updateCalorieCalculations()); - ageInput.addEventListener('blur', () => this.updateCalorieCalculations()); + ageInput.addEventListener('input', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); }); + ageInput.addEventListener('blur', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); }); } if (daysInput) { daysInput.addEventListener('input', () => { this.updateDayLabel(); this.updateFoodCalculations(); + this.saveStateToStorage(); }); daysInput.addEventListener('blur', () => this.validateDays()); } - if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations()); + if (unitSelect) unitSelect.addEventListener('change', () => { this.updateFoodCalculations(); this.saveStateToStorage(); }); // Unit button event listeners const unitButtons = document.querySelectorAll('.dog-calculator-unit-btn'); @@ -873,6 +1002,7 @@ if (unitSelect) { unitSelect.value = selectedUnit; this.updateFoodCalculations(); + this.saveStateToStorage(); } }); });