Add lock
This commit is contained in:
parent
119f1905ec
commit
f3baa12bd3
193
iframe.html
193
iframe.html
@ -1264,6 +1264,66 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lock Icon Styles */
|
||||||
|
.dog-calculator-lock-icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-lock-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-lock-icon.locked {
|
||||||
|
color: #f19a5f;
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-lock-icon.unlocked {
|
||||||
|
color: #635870;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-lock-icon.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-lock-icon.disabled:hover {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme support for lock icons */
|
||||||
|
.dog-calculator-container.theme-dark .dog-calculator-lock-icon.unlocked {
|
||||||
|
color: #b8b0c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-container.theme-dark .dog-calculator-lock-icon.locked {
|
||||||
|
color: #f19a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* System theme support for lock icons */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.dog-calculator-container.theme-system .dog-calculator-lock-icon.unlocked {
|
||||||
|
color: #b8b0c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dog-calculator-container.theme-system .dog-calculator-lock-icon.locked {
|
||||||
|
color: #f19a5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -1534,7 +1594,8 @@
|
|||||||
name: `Food Source ${this.foodSources.length + 1}`,
|
name: `Food Source ${this.foodSources.length + 1}`,
|
||||||
energy: '',
|
energy: '',
|
||||||
energyUnit: this.isImperial ? 'kcalcup' : 'kcal100g',
|
energyUnit: this.isImperial ? 'kcalcup' : 'kcal100g',
|
||||||
percentage: this.foodSources.length === 0 ? 100 : 0
|
percentage: this.foodSources.length === 0 ? 100 : 0,
|
||||||
|
isLocked: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.foodSources.push(foodSource);
|
this.foodSources.push(foodSource);
|
||||||
@ -1575,12 +1636,24 @@
|
|||||||
const count = this.foodSources.length;
|
const count = this.foodSources.length;
|
||||||
if (count === 0) return;
|
if (count === 0) return;
|
||||||
|
|
||||||
const equalPercentage = Math.floor(100 / count);
|
// Only redistribute among unlocked sources
|
||||||
const remainder = 100 - (equalPercentage * count);
|
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
|
||||||
|
const lockedSources = this.foodSources.filter(fs => fs.isLocked);
|
||||||
|
|
||||||
|
// Calculate total locked percentage
|
||||||
|
const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||||
|
|
||||||
|
// Available percentage for unlocked sources
|
||||||
|
const availablePercentage = 100 - totalLockedPercentage;
|
||||||
|
|
||||||
|
if (unlockedSources.length > 0) {
|
||||||
|
const equalPercentage = Math.floor(availablePercentage / unlockedSources.length);
|
||||||
|
const remainder = availablePercentage - (equalPercentage * unlockedSources.length);
|
||||||
|
|
||||||
this.foodSources.forEach((fs, index) => {
|
unlockedSources.forEach((fs, index) => {
|
||||||
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
|
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update the UI sliders and inputs
|
// Update the UI sliders and inputs
|
||||||
this.updatePercentageInputs();
|
this.updatePercentageInputs();
|
||||||
@ -1605,31 +1678,41 @@
|
|||||||
|
|
||||||
this.foodSources[changedIndex].percentage = newPercentage;
|
this.foodSources[changedIndex].percentage = newPercentage;
|
||||||
|
|
||||||
// Distribute the difference among other sources proportionally
|
// Only redistribute among unlocked sources (excluding the changed one)
|
||||||
const otherSources = this.foodSources.filter((fs, index) => index !== changedIndex);
|
const otherUnlockedSources = this.foodSources.filter((fs, index) =>
|
||||||
if (otherSources.length === 0) return;
|
index !== changedIndex && !fs.isLocked
|
||||||
|
);
|
||||||
const totalOtherPercentage = otherSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
|
||||||
|
|
||||||
if (totalOtherPercentage === 0) {
|
if (otherUnlockedSources.length === 0) return;
|
||||||
// If all others are 0, distribute equally
|
|
||||||
const remainingPercentage = 100 - newPercentage;
|
// Calculate total locked percentage (excluding the changed source)
|
||||||
const equalShare = Math.floor(remainingPercentage / otherSources.length);
|
const lockedSources = this.foodSources.filter((fs, index) =>
|
||||||
const remainder = remainingPercentage - (equalShare * otherSources.length);
|
index !== changedIndex && fs.isLocked
|
||||||
|
);
|
||||||
|
const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||||
|
|
||||||
|
// Available percentage for unlocked sources
|
||||||
|
const availablePercentage = 100 - newPercentage - totalLockedPercentage;
|
||||||
|
|
||||||
|
const totalUnlockedPercentage = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||||
|
|
||||||
|
if (totalUnlockedPercentage === 0) {
|
||||||
|
// If all other unlocked sources are 0, distribute equally
|
||||||
|
const equalShare = Math.floor(availablePercentage / otherUnlockedSources.length);
|
||||||
|
const remainder = availablePercentage - (equalShare * otherUnlockedSources.length);
|
||||||
|
|
||||||
otherSources.forEach((fs, index) => {
|
otherUnlockedSources.forEach((fs, index) => {
|
||||||
fs.percentage = equalShare + (index < remainder ? 1 : 0);
|
fs.percentage = equalShare + (index < remainder ? 1 : 0);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Distribute proportionally
|
// Distribute proportionally among unlocked sources
|
||||||
const targetTotal = 100 - newPercentage;
|
const scale = availablePercentage / totalUnlockedPercentage;
|
||||||
const scale = targetTotal / totalOtherPercentage;
|
|
||||||
|
|
||||||
let distributedTotal = 0;
|
let distributedTotal = 0;
|
||||||
otherSources.forEach((fs, index) => {
|
otherUnlockedSources.forEach((fs, index) => {
|
||||||
if (index === otherSources.length - 1) {
|
if (index === otherUnlockedSources.length - 1) {
|
||||||
// Last item gets the remainder to ensure exact 100%
|
// Last item gets the remainder to ensure exact 100%
|
||||||
fs.percentage = targetTotal - distributedTotal;
|
fs.percentage = availablePercentage - distributedTotal;
|
||||||
} else {
|
} else {
|
||||||
fs.percentage = Math.round(fs.percentage * scale);
|
fs.percentage = Math.round(fs.percentage * scale);
|
||||||
distributedTotal += fs.percentage;
|
distributedTotal += fs.percentage;
|
||||||
@ -1700,6 +1783,7 @@
|
|||||||
<div class="dog-calculator-percentage-group">
|
<div class="dog-calculator-percentage-group">
|
||||||
<label class="dog-calculator-percentage-label" for="percentage-slider-${foodSource.id}">
|
<label class="dog-calculator-percentage-label" for="percentage-slider-${foodSource.id}">
|
||||||
Percentage of Diet: <span id="percentage-display-${foodSource.id}">${foodSource.percentage}%</span>
|
Percentage of Diet: <span id="percentage-display-${foodSource.id}">${foodSource.percentage}%</span>
|
||||||
|
<span class="dog-calculator-lock-icon unlocked" id="lock-${foodSource.id}" title="Lock this percentage">🔒</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="dog-calculator-percentage-input-group">
|
<div class="dog-calculator-percentage-input-group">
|
||||||
<input type="range" id="percentage-slider-${foodSource.id}" class="dog-calculator-percentage-slider"
|
<input type="range" id="percentage-slider-${foodSource.id}" class="dog-calculator-percentage-slider"
|
||||||
@ -1724,6 +1808,7 @@
|
|||||||
const percentageSlider = document.getElementById(`percentage-slider-${id}`);
|
const percentageSlider = document.getElementById(`percentage-slider-${id}`);
|
||||||
const percentageInput = document.getElementById(`percentage-input-${id}`);
|
const percentageInput = document.getElementById(`percentage-input-${id}`);
|
||||||
const removeBtn = document.getElementById(`remove-${id}`);
|
const removeBtn = document.getElementById(`remove-${id}`);
|
||||||
|
const lockBtn = document.getElementById(`lock-${id}`);
|
||||||
|
|
||||||
if (energyInput) {
|
if (energyInput) {
|
||||||
energyInput.addEventListener('input', () => {
|
energyInput.addEventListener('input', () => {
|
||||||
@ -1759,6 +1844,63 @@
|
|||||||
if (removeBtn) {
|
if (removeBtn) {
|
||||||
removeBtn.addEventListener('click', () => this.removeFoodSource(id));
|
removeBtn.addEventListener('click', () => this.removeFoodSource(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lockBtn) {
|
||||||
|
lockBtn.addEventListener('click', () => this.toggleLock(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLock(id) {
|
||||||
|
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||||
|
if (!foodSource) return;
|
||||||
|
|
||||||
|
// Check if we're trying to lock the last unlocked source
|
||||||
|
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
|
||||||
|
if (unlockedSources.length === 1 && unlockedSources[0].id === id) {
|
||||||
|
// Cannot lock the last unlocked source
|
||||||
|
alert('At least one food source must remain flexible for percentage adjustments.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle lock state
|
||||||
|
foodSource.isLocked = !foodSource.isLocked;
|
||||||
|
this.updateLockIcon(id);
|
||||||
|
this.updateLockStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLockIcon(id) {
|
||||||
|
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||||
|
const lockIcon = document.getElementById(`lock-${id}`);
|
||||||
|
|
||||||
|
if (!lockIcon || !foodSource) return;
|
||||||
|
|
||||||
|
if (foodSource.isLocked) {
|
||||||
|
lockIcon.classList.remove('unlocked');
|
||||||
|
lockIcon.classList.add('locked');
|
||||||
|
lockIcon.title = 'Unlock this percentage';
|
||||||
|
} else {
|
||||||
|
lockIcon.classList.remove('locked');
|
||||||
|
lockIcon.classList.add('unlocked');
|
||||||
|
lockIcon.title = 'Lock this percentage';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLockStates() {
|
||||||
|
const unlockedSources = this.foodSources.filter(fs => !fs.isLocked);
|
||||||
|
|
||||||
|
// Update lock icon states - disable lock for last unlocked source
|
||||||
|
this.foodSources.forEach(fs => {
|
||||||
|
const lockIcon = document.getElementById(`lock-${fs.id}`);
|
||||||
|
if (lockIcon) {
|
||||||
|
if (!fs.isLocked && unlockedSources.length === 1) {
|
||||||
|
lockIcon.classList.add('disabled');
|
||||||
|
lockIcon.title = 'Cannot lock - at least one source must remain flexible';
|
||||||
|
} else {
|
||||||
|
lockIcon.classList.remove('disabled');
|
||||||
|
lockIcon.title = fs.isLocked ? 'Unlock this percentage' : 'Lock this percentage';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFoodSourceData(id, field, value) {
|
updateFoodSourceData(id, field, value) {
|
||||||
@ -2123,7 +2265,8 @@
|
|||||||
name: fs.name,
|
name: fs.name,
|
||||||
percentage: fs.percentage,
|
percentage: fs.percentage,
|
||||||
dailyGrams: dailyGramsForThisFood,
|
dailyGrams: dailyGramsForThisFood,
|
||||||
calories: dailyCaloriesForThisFood
|
calories: dailyCaloriesForThisFood,
|
||||||
|
isLocked: fs.isLocked
|
||||||
});
|
});
|
||||||
|
|
||||||
totalDailyGrams += dailyGramsForThisFood;
|
totalDailyGrams += dailyGramsForThisFood;
|
||||||
@ -2154,7 +2297,7 @@
|
|||||||
if (foodBreakdownList && foodBreakdowns.length > 1) {
|
if (foodBreakdownList && foodBreakdowns.length > 1) {
|
||||||
const breakdownHTML = foodBreakdowns.map(breakdown => `
|
const breakdownHTML = foodBreakdowns.map(breakdown => `
|
||||||
<div class="dog-calculator-food-result-item">
|
<div class="dog-calculator-food-result-item">
|
||||||
<span class="dog-calculator-food-result-label">${breakdown.name} (${breakdown.percentage}%):</span>
|
<span class="dog-calculator-food-result-label">${breakdown.name} (${breakdown.percentage}%${breakdown.isLocked ? ' - locked' : ''}):</span>
|
||||||
<span class="dog-calculator-food-result-value">${this.formatNumber(breakdown.dailyGrams, 1)} g/day</span>
|
<span class="dog-calculator-food-result-value">${this.formatNumber(breakdown.dailyGrams, 1)} g/day</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user