Add local stoarge

This commit is contained in:
Dayowe 2025-11-12 18:39:10 +01:00
parent 374d067cf4
commit d0a30a8f8d
2 changed files with 318 additions and 58 deletions

View File

@ -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();
}
});
});

View File

@ -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();
}
});
});