mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-13 10:15:29 +00:00
fix(ui): correct charge counter rounding and status updates
- Fix charge counter inconsistencies between Status block and Stats section. - Implement rounding with a 0.95 threshold instead of always flooring. - Prevent Stats counter from showing -1 when painting reaches the last pixel. - Ensure Status UI updates correctly during painting progress (instead of being stuck on "Starting painting"). - Refactor charge calculation code for clarity and maintainability. - Standardize dynamic sleep interval handling with a minimum threshold of 100ms. Benefits: - Accurate and consistent charge display across UI components. - More reliable progress updates during painting. - Cleaner, more maintainable codebase.
This commit is contained in:
1 parent
094932b2dc
commit
4acfdf4452
1 file changed
+107
-50
+107
-50
@@ -31,8 +31,7 @@
|
||||
OPACITY_DEFAULT: 0.2,
|
||||
BLUE_MARBLE_DEFAULT: false,
|
||||
ditheringEnabled: false,
|
||||
},
|
||||
// --- START: Color data from colour-converter.js ---
|
||||
}, // --- START: Color data from colour-converter.js ---
|
||||
// New color structure with proper ID mapping
|
||||
COLOR_MAP: {
|
||||
0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } },
|
||||
@@ -99,8 +98,7 @@
|
||||
61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } },
|
||||
62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } },
|
||||
63: { id: 0, name: 'Transparent', rgb: null },
|
||||
},
|
||||
// --- END: Color data ---
|
||||
}, // --- END: Color data ---
|
||||
// Optimized CSS Classes for reuse
|
||||
CSS_CLASSES: {
|
||||
BUTTON_PRIMARY: `
|
||||
@@ -329,7 +327,9 @@
|
||||
) {
|
||||
loadedTranslations[language] = translations;
|
||||
console.log(
|
||||
`📚 Loaded ${language} translations successfully from CDN (${Object.keys(translations).length} keys)`
|
||||
`📚 Loaded ${language} translations successfully from CDN (${
|
||||
Object.keys(translations).length
|
||||
} keys)`
|
||||
);
|
||||
return translations;
|
||||
} else {
|
||||
@@ -1295,7 +1295,7 @@
|
||||
|
||||
dynamicSleep: async function (getRemainingMsFn, interval = 500) {
|
||||
let remaining = getRemainingMsFn();
|
||||
while (remaining > 0) {
|
||||
while (remaining > 100) {
|
||||
await this.sleep(Math.min(interval, remaining));
|
||||
remaining = getRemainingMsFn();
|
||||
}
|
||||
@@ -1320,12 +1320,7 @@
|
||||
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
||||
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
||||
return `${seconds}s`;
|
||||
}, // Turnstile Generator Integration - Optimized with widget reuse and proper cleanup
|
||||
turnstileLoaded: false,
|
||||
_turnstileContainer: null,
|
||||
_turnstileOverlay: null,
|
||||
_turnstileWidgetId: null,
|
||||
_lastSitekey: null,
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate the range of tile coordinates (in region space) that cover a given image area.
|
||||
@@ -1356,7 +1351,12 @@
|
||||
endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize),
|
||||
endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize),
|
||||
};
|
||||
},
|
||||
}, // Turnstile Generator Integration - Optimized with widget reuse and proper cleanup
|
||||
turnstileLoaded: false,
|
||||
_turnstileContainer: null,
|
||||
_turnstileOverlay: null,
|
||||
_turnstileWidgetId: null,
|
||||
_lastSitekey: null,
|
||||
|
||||
async loadTurnstile() {
|
||||
// If Turnstile is already present, just resolve.
|
||||
@@ -2872,7 +2872,7 @@
|
||||
const shouldRepeat = now - (state._lastChargesNotifyAt || 0) >= repeatMs;
|
||||
if (shouldEdge || shouldRepeat) {
|
||||
const msg = Utils.t('chargesReadyMessage', {
|
||||
current: Math.floor(state.currentCharges),
|
||||
current: state.currentCharges,
|
||||
max: state.maxCharges,
|
||||
threshold: state.cooldownChargeThreshold,
|
||||
});
|
||||
@@ -2977,7 +2977,9 @@
|
||||
// Don't re-fetch colors here, use what was captured when user clicked upload
|
||||
if (!state.availableColors || state.availableColors.length === 0) {
|
||||
// If no colors have been captured yet, show message
|
||||
colorsContainer.innerHTML = `<div class="wplace-colors-placeholder">${Utils.t('uploadImageFirst')}</div>`;
|
||||
colorsContainer.innerHTML = `<div class="wplace-colors-placeholder">${Utils.t(
|
||||
'uploadImageFirst'
|
||||
)}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3349,7 +3351,9 @@
|
||||
<div class="wplace-section-title">🖼️ Image Management</div>
|
||||
<div class="wplace-controls">
|
||||
<div class="wplace-row">
|
||||
<button id="uploadBtn" class="wplace-btn wplace-btn-upload" disabled title="${Utils.t('waitingSetupComplete')}">
|
||||
<button id="uploadBtn" class="wplace-btn wplace-btn-upload" disabled title="${Utils.t(
|
||||
'waitingSetupComplete'
|
||||
)}">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span>${Utils.t('uploadImage')}</span>
|
||||
</button>
|
||||
@@ -3411,7 +3415,9 @@
|
||||
<i class="fas fa-save"></i>
|
||||
<span>${Utils.t('saveData')}</span>
|
||||
</button>
|
||||
<button id="loadBtn" class="wplace-btn wplace-btn-primary" disabled title="${Utils.t('waitingTokenGenerator')}">
|
||||
<button id="loadBtn" class="wplace-btn wplace-btn-primary" disabled title="${Utils.t(
|
||||
'waitingTokenGenerator'
|
||||
)}">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>${Utils.t('loadData')}</span>
|
||||
</button>
|
||||
@@ -3421,7 +3427,9 @@
|
||||
<i class="fas fa-download"></i>
|
||||
<span>${Utils.t('saveToFile')}</span>
|
||||
</button>
|
||||
<button id="loadFromFileBtn" class="wplace-btn wplace-btn-file" disabled title="${Utils.t('waitingTokenGenerator')}">
|
||||
<button id="loadFromFileBtn" class="wplace-btn wplace-btn-file" disabled title="${Utils.t(
|
||||
'waitingTokenGenerator'
|
||||
)}">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span>${Utils.t('loadFromFile')}</span>
|
||||
</button>
|
||||
@@ -3442,7 +3450,9 @@
|
||||
<span>${Utils.t('paintingStats')}</span>
|
||||
</div>
|
||||
<div class="wplace-header-controls">
|
||||
<button id="refreshChargesBtn" class="wplace-header-btn" title="${Utils.t('refreshCharges')}">
|
||||
<button id="refreshChargesBtn" class="wplace-header-btn" title="${Utils.t(
|
||||
'refreshCharges'
|
||||
)}">
|
||||
<i class="fas fa-sync"></i>
|
||||
</button>
|
||||
<button id="closeStatsBtn" class="wplace-header-btn" title="${Utils.t('closeStats')}">
|
||||
@@ -3683,7 +3693,9 @@
|
||||
|
||||
<!-- Speed Control Toggle -->
|
||||
<label class="wplace-speed-control-toggle">
|
||||
<input type="checkbox" id="enableSpeedToggle" ${CONFIG.PAINTING_SPEED_ENABLED ? 'checked' : ''} class="wplace-speed-checkbox"/>
|
||||
<input type="checkbox" id="enableSpeedToggle" ${
|
||||
CONFIG.PAINTING_SPEED_ENABLED ? 'checked' : ''
|
||||
} class="wplace-speed-checkbox"/>
|
||||
<span>${Utils.t('enablePaintingSpeedLimit')}</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -3778,15 +3790,21 @@
|
||||
<div class="wplace-settings-section-wrapper wplace-notifications-wrapper">
|
||||
<label class="wplace-notification-toggle">
|
||||
<span>${Utils.t('enableNotifications')}</span>
|
||||
<input type="checkbox" id="notifEnabledToggle" ${state.notificationsEnabled ? 'checked' : ''} class="wplace-notification-checkbox" />
|
||||
<input type="checkbox" id="notifEnabledToggle" ${
|
||||
state.notificationsEnabled ? 'checked' : ''
|
||||
} class="wplace-notification-checkbox" />
|
||||
</label>
|
||||
<label class="wplace-notification-toggle">
|
||||
<span>${Utils.t('notifyOnChargesThreshold')}</span>
|
||||
<input type="checkbox" id="notifOnChargesToggle" ${state.notifyOnChargesReached ? 'checked' : ''} class="wplace-notification-checkbox" />
|
||||
<input type="checkbox" id="notifOnChargesToggle" ${
|
||||
state.notifyOnChargesReached ? 'checked' : ''
|
||||
} class="wplace-notification-checkbox" />
|
||||
</label>
|
||||
<label class="wplace-notification-toggle">
|
||||
<span>${Utils.t('onlyWhenNotFocused')}</span>
|
||||
<input type="checkbox" id="notifOnlyUnfocusedToggle" ${state.notifyOnlyWhenUnfocused ? 'checked' : ''} class="wplace-notification-checkbox" />
|
||||
<input type="checkbox" id="notifOnlyUnfocusedToggle" ${
|
||||
state.notifyOnlyWhenUnfocused ? 'checked' : ''
|
||||
} class="wplace-notification-checkbox" />
|
||||
</label>
|
||||
<div class="wplace-notification-interval">
|
||||
<span>${Utils.t('repeatEvery')}</span>
|
||||
@@ -3794,8 +3812,12 @@
|
||||
<span>${Utils.t('minutesPl')}</span>
|
||||
</div>
|
||||
<div class="wplace-notification-buttons">
|
||||
<button id="notifRequestPermBtn" class="wplace-btn wplace-btn-secondary wplace-notification-perm-btn"><i class="fas fa-unlock"></i><span>${Utils.t('grantPermission')}</span></button>
|
||||
<button id="notifTestBtn" class="wplace-btn wplace-notification-test-btn"><i class="fas fa-bell"></i><span>${Utils.t('test')}</span></button>
|
||||
<button id="notifRequestPermBtn" class="wplace-btn wplace-btn-secondary wplace-notification-perm-btn"><i class="fas fa-unlock"></i><span>${Utils.t(
|
||||
'grantPermission'
|
||||
)}</span></button>
|
||||
<button id="notifTestBtn" class="wplace-btn wplace-notification-test-btn"><i class="fas fa-bell"></i><span>${Utils.t(
|
||||
'test'
|
||||
)}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3974,11 +3996,19 @@
|
||||
${Utils.t('paintTransparentPixels')}
|
||||
</label>
|
||||
<div class="resize-zoom-controls">
|
||||
<button id="zoomOutBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t('zoomOut')}"><i class="fas fa-search-minus"></i></button>
|
||||
<button id="zoomOutBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t(
|
||||
'zoomOut'
|
||||
)}"><i class="fas fa-search-minus"></i></button>
|
||||
<input type="range" id="zoomSlider" class="resize-slider resize-zoom-slider" min="0.1" max="20" value="1" step="0.05">
|
||||
<button id="zoomInBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t('zoomIn')}"><i class="fas fa-search-plus"></i></button>
|
||||
<button id="zoomFitBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t('fitToView')}">${Utils.t('fit')}</button>
|
||||
<button id="zoomActualBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t('actualSize')}">${Utils.t('hundred')}</button>
|
||||
<button id="zoomInBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t(
|
||||
'zoomIn'
|
||||
)}"><i class="fas fa-search-plus"></i></button>
|
||||
<button id="zoomFitBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t(
|
||||
'fitToView'
|
||||
)}">${Utils.t('fit')}</button>
|
||||
<button id="zoomActualBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t(
|
||||
'actualSize'
|
||||
)}">${Utils.t('hundred')}</button>
|
||||
<button id="panModeBtn" class="wplace-btn resize-zoom-btn" title="${Utils.t('panMode')}">
|
||||
<i class="fas fa-hand-paper"></i>
|
||||
</button>
|
||||
@@ -4897,20 +4927,29 @@
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remainingMs = Math.max(0, (max - current) * cooldownMs - elapsed);
|
||||
|
||||
let timeText = Utils.msToTimeText(remainingMs);
|
||||
const timeText = Utils.msToTimeText(remainingMs);
|
||||
|
||||
const chargesGained = Math.ceil(elapsed / cooldownMs);
|
||||
const currentCharges = Math.min(current + chargesGained, max);
|
||||
// total charges including elapsed time
|
||||
const chargesGained = elapsed / cooldownMs;
|
||||
const rawCharges = current + chargesGained;
|
||||
const cappedCharges = Math.min(rawCharges, max);
|
||||
|
||||
state.currentCharges = Math.floor(currentCharges);
|
||||
// rounding with 0.95 threshold
|
||||
let displayCharges;
|
||||
const fraction = cappedCharges - Math.floor(cappedCharges);
|
||||
if (fraction >= 0.95) {
|
||||
displayCharges = Math.ceil(cappedCharges);
|
||||
} else {
|
||||
displayCharges = Math.floor(cappedCharges);
|
||||
}
|
||||
|
||||
state.currentCharges = displayCharges;
|
||||
const chargesEl = document.getElementById('wplace-stat-charges-value');
|
||||
const secondsInMinute = Math.ceil(remainingMs / 1000) % 60;
|
||||
if (chargesEl && (secondsInMinute === 0 || secondsInMinute === 30)) {
|
||||
chargesEl.innerHTML = `<span">${Math.floor(
|
||||
state.currentCharges
|
||||
)} / ${state.maxCharges}</span>`;
|
||||
chargesEl.innerHTML = `<span">${state.currentCharges} / ${state.maxCharges}</span>`;
|
||||
}
|
||||
if (currentCharges >= max) {
|
||||
if (state.currentCharges >= max) {
|
||||
fullChargeEl.innerHTML = `<span style="color:#10b981;">FULL</span>`;
|
||||
clearInterval(state.fullChargeInterval);
|
||||
state.fullChargeInterval = null;
|
||||
@@ -5003,7 +5042,10 @@
|
||||
colorSwatchesHTML = state.availableColors
|
||||
.map((color) => {
|
||||
const rgbString = `rgb(${color.rgb.join(',')})`;
|
||||
return `<div class="wplace-stat-color-swatch" style="background-color: ${rgbString};" title="${Utils.t('colorTooltip', { id: color.id, rgb: color.rgb.join(', ') })}"></div>`;
|
||||
return `<div class="wplace-stat-color-swatch" style="background-color: ${rgbString};" title="${Utils.t(
|
||||
'colorTooltip',
|
||||
{ id: color.id, rgb: color.rgb.join(', ') }
|
||||
)}"></div>`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
@@ -5021,7 +5063,7 @@
|
||||
<i class="fas fa-bolt"></i> ${Utils.t('charges')}
|
||||
</div>
|
||||
<div class="wplace-stat-value" id="wplace-stat-charges-value">
|
||||
${Math.floor(state.currentCharges)} / ${state.maxCharges}
|
||||
${state.currentCharges} / ${state.maxCharges}
|
||||
</div>
|
||||
</div>
|
||||
<div class="wplace-stat-item">
|
||||
@@ -5036,7 +5078,10 @@
|
||||
state.colorsChecked
|
||||
? `
|
||||
<div class="wplace-colors-section">
|
||||
<div class="wplace-stat-label"><i class="fas fa-palette"></i> ${Utils.t('availableColors', { count: state.availableColors.length })}</div>
|
||||
<div class="wplace-stat-label"><i class="fas fa-palette"></i> ${Utils.t(
|
||||
'availableColors',
|
||||
{ count: state.availableColors.length }
|
||||
)}</div>
|
||||
<div class="wplace-stat-colors-grid">
|
||||
${colorSwatchesHTML}
|
||||
</div>
|
||||
@@ -6483,11 +6528,9 @@
|
||||
if (state.chargesThresholdInterval) {
|
||||
clearInterval(state.chargesThresholdInterval);
|
||||
}
|
||||
updateUI('startPaintingMsg', 'success');
|
||||
return; // Threshold already reached
|
||||
}
|
||||
|
||||
const current = state.fullChargeData ? state.fullChargeData.current : state.currentCharges;
|
||||
const timeText = Utils.msToTimeText(remainingMs);
|
||||
|
||||
updateUI(
|
||||
@@ -6495,7 +6538,7 @@
|
||||
'warning',
|
||||
{
|
||||
threshold,
|
||||
current: Math.floor(current),
|
||||
current: state.currentCharges,
|
||||
time: timeText,
|
||||
},
|
||||
true
|
||||
@@ -6671,6 +6714,10 @@
|
||||
});
|
||||
state.currentCharges -= batchSize;
|
||||
updateStats();
|
||||
updateUI('paintingProgress', 'default', {
|
||||
painted: state.paintedPixels,
|
||||
total: state.totalPixels,
|
||||
});
|
||||
Utils.performSmartSave();
|
||||
|
||||
if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && batchSize > 0) {
|
||||
@@ -6793,6 +6840,7 @@
|
||||
clearInterval(state.chargesThresholdInterval);
|
||||
}
|
||||
updateUI('paintingPaused', 'warning', { x, y });
|
||||
// noinspection UnnecessaryLabelOnBreakStatementJS
|
||||
break outerLoop;
|
||||
}
|
||||
|
||||
@@ -6848,6 +6896,7 @@
|
||||
console.error(`❌ Batch failed permanently after retries. Stopping painting.`);
|
||||
state.stopFlag = true;
|
||||
updateUI('paintingBatchFailed', 'error');
|
||||
// noinspection UnnecessaryLabelOnBreakStatementJS
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
@@ -6884,16 +6933,21 @@
|
||||
continue;
|
||||
}
|
||||
console.debug(
|
||||
`[COMPARE] Pixel at 📍 (${pixelX}, ${pixelY}) in region (${regionX + adderX}, ${regionY + adderY})\n` +
|
||||
`[COMPARE] Pixel at 📍 (${pixelX}, ${pixelY}) in region (${
|
||||
regionX + adderX
|
||||
}, ${regionY + adderY})\n` +
|
||||
` ├── Current color: rgb(${er}, ${eg}, ${eb}) (id: ${existingColor.id})\n` +
|
||||
` ├── Target color: rgb(${targetPixelInfo.r}, ${targetPixelInfo.g}, ${targetPixelInfo.b}) (id: ${targetColorId})\n` +
|
||||
` └── Status: ${isMatch ? '✅ Already painted → SKIP' : '🔴 Needs paint → PAINT'}\n`
|
||||
` └── Status: ${
|
||||
isMatch ? '✅ Already painted → SKIP' : '🔴 Needs paint → PAINT'
|
||||
}\n`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[DEBUG] Error checking existing pixel at (${pixelX}, ${pixelY}):`, e);
|
||||
updateUI('paintingPixelCheckFailed', 'error', { x: pixelX, y: pixelY });
|
||||
state.stopFlag = true;
|
||||
// noinspection UnnecessaryLabelOnBreakStatementJS
|
||||
break outerLoop;
|
||||
}
|
||||
|
||||
@@ -6919,6 +6973,7 @@
|
||||
console.error(`❌ Batch failed permanently after retries. Stopping painting.`);
|
||||
state.stopFlag = true;
|
||||
updateUI('paintingBatchFailed', 'error');
|
||||
// noinspection UnnecessaryLabelOnBreakStatementJS
|
||||
break outerLoop;
|
||||
}
|
||||
|
||||
@@ -6927,21 +6982,23 @@
|
||||
|
||||
while (state.currentCharges < state.cooldownChargeThreshold && !state.stopFlag) {
|
||||
await updateStats();
|
||||
|
||||
if (state.currentCharges >= state.cooldownChargeThreshold) {
|
||||
NotificationManager.maybeNotifyChargesReached(true);
|
||||
break;
|
||||
}
|
||||
|
||||
saveBtn.disabled = false;
|
||||
startChargesThresholdTicker();
|
||||
|
||||
Utils.performSmartSave();
|
||||
|
||||
await Utils.dynamicSleep(() => getRemainingMsToThreshold(state.cooldownChargeThreshold));
|
||||
await Utils.dynamicSleep(
|
||||
() => getRemainingMsToThreshold(state.cooldownChargeThreshold),
|
||||
1000
|
||||
);
|
||||
}
|
||||
|
||||
if (!state.stopFlag) saveBtn.disabled = true;
|
||||
if (state.stopFlag) {
|
||||
// noinspection UnnecessaryLabelOnBreakStatementJS
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
@@ -7014,7 +7071,7 @@
|
||||
}
|
||||
|
||||
// Always limit by available charges
|
||||
const maxAllowed = Math.floor(state.currentCharges);
|
||||
const maxAllowed = state.currentCharges;
|
||||
const finalBatchSize = Math.min(targetBatchSize, maxAllowed);
|
||||
|
||||
return finalBatchSize;
|
||||
|
||||
Reference in new issue
Block a user