Add lock
This commit is contained in:
parent
119f1905ec
commit
f3baa12bd3
193
iframe.html
193
iframe.html
@ -1264,6 +1264,66 @@
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
@ -1534,7 +1594,8 @@
|
||||
name: `Food Source ${this.foodSources.length + 1}`,
|
||||
energy: '',
|
||||
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);
|
||||
@ -1575,12 +1636,24 @@
|
||||
const count = this.foodSources.length;
|
||||
if (count === 0) return;
|
||||
|
||||
const equalPercentage = Math.floor(100 / count);
|
||||
const remainder = 100 - (equalPercentage * count);
|
||||
// Only redistribute among unlocked sources
|
||||
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) => {
|
||||
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
|
||||
});
|
||||
unlockedSources.forEach((fs, index) => {
|
||||
fs.percentage = equalPercentage + (index < remainder ? 1 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Update the UI sliders and inputs
|
||||
this.updatePercentageInputs();
|
||||
@ -1605,31 +1678,41 @@
|
||||
|
||||
this.foodSources[changedIndex].percentage = newPercentage;
|
||||
|
||||
// Distribute the difference among other sources proportionally
|
||||
const otherSources = this.foodSources.filter((fs, index) => index !== changedIndex);
|
||||
if (otherSources.length === 0) return;
|
||||
|
||||
const totalOtherPercentage = otherSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
// Only redistribute among unlocked sources (excluding the changed one)
|
||||
const otherUnlockedSources = this.foodSources.filter((fs, index) =>
|
||||
index !== changedIndex && !fs.isLocked
|
||||
);
|
||||
|
||||
if (totalOtherPercentage === 0) {
|
||||
// If all others are 0, distribute equally
|
||||
const remainingPercentage = 100 - newPercentage;
|
||||
const equalShare = Math.floor(remainingPercentage / otherSources.length);
|
||||
const remainder = remainingPercentage - (equalShare * otherSources.length);
|
||||
if (otherUnlockedSources.length === 0) return;
|
||||
|
||||
// Calculate total locked percentage (excluding the changed source)
|
||||
const lockedSources = this.foodSources.filter((fs, index) =>
|
||||
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);
|
||||
});
|
||||
} else {
|
||||
// Distribute proportionally
|
||||
const targetTotal = 100 - newPercentage;
|
||||
const scale = targetTotal / totalOtherPercentage;
|
||||
// Distribute proportionally among unlocked sources
|
||||
const scale = availablePercentage / totalUnlockedPercentage;
|
||||
|
||||
let distributedTotal = 0;
|
||||
otherSources.forEach((fs, index) => {
|
||||
if (index === otherSources.length - 1) {
|
||||
otherUnlockedSources.forEach((fs, index) => {
|
||||
if (index === otherUnlockedSources.length - 1) {
|
||||
// Last item gets the remainder to ensure exact 100%
|
||||
fs.percentage = targetTotal - distributedTotal;
|
||||
fs.percentage = availablePercentage - distributedTotal;
|
||||
} else {
|
||||
fs.percentage = Math.round(fs.percentage * scale);
|
||||
distributedTotal += fs.percentage;
|
||||
@ -1700,6 +1783,7 @@
|
||||
<div class="dog-calculator-percentage-group">
|
||||
<label class="dog-calculator-percentage-label" for="percentage-slider-${foodSource.id}">
|
||||
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>
|
||||
<div class="dog-calculator-percentage-input-group">
|
||||
<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 percentageInput = document.getElementById(`percentage-input-${id}`);
|
||||
const removeBtn = document.getElementById(`remove-${id}`);
|
||||
const lockBtn = document.getElementById(`lock-${id}`);
|
||||
|
||||
if (energyInput) {
|
||||
energyInput.addEventListener('input', () => {
|
||||
@ -1759,6 +1844,63 @@
|
||||
if (removeBtn) {
|
||||
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) {
|
||||
@ -2123,7 +2265,8 @@
|
||||
name: fs.name,
|
||||
percentage: fs.percentage,
|
||||
dailyGrams: dailyGramsForThisFood,
|
||||
calories: dailyCaloriesForThisFood
|
||||
calories: dailyCaloriesForThisFood,
|
||||
isLocked: fs.isLocked
|
||||
});
|
||||
|
||||
totalDailyGrams += dailyGramsForThisFood;
|
||||
@ -2154,7 +2297,7 @@
|
||||
if (foodBreakdownList && foodBreakdowns.length > 1) {
|
||||
const breakdownHTML = foodBreakdowns.map(breakdown => `
|
||||
<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>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user