refactor: improve readability, logging, and pixel/color processing

* Consolidated multi-line comments into single-line comments where appropriate to reduce clutter.
* Simplified and clarified inline comments for better code readability and maintainability.
* Refactored sitekey detection logic to use a reusable `trySitekey` function, reducing redundancy and improving maintainability.
* Enhanced fallback logic in `detectSitekey` to handle edge cases more effectively.
* Updated token generation logic to prioritize reusing pre-generated or cached tokens, optimizing performance.
* Added detailed debug logs for Turnstile token capture, token generation, validation processes, and pixel comparison.
* Improved formatting of complex log messages, dynamic strings, and template literals for better readability and debugging.
* Reworked how available colors are extracted and validated to ensure proper handling of edge cases.
* Added checks for null or insufficient color data during upload, improving error feedback to users.
* Enhanced skipped pixel tracking by restructuring logic for transparency, white pixels, and unavailable colors.
* Refactored batch processing to handle region changes more efficiently and log progress accurately.
* Updated color extraction logic to handle scenarios where no color elements are found.
* Simplified and standardized checkbox, select, slider, and toggle rendering for settings UI.
* Ensured proper alignment and spacing in multi-line template literals for themes, languages, and other dropdowns.
* Fixed minor typos, inconsistencies, and formatting issues across multiple modules.

These changes improve maintainability, debugging visibility, and robustness of pixel/color processing while enhancing overall user experience without altering core functionality.
This commit is contained in:
kyuketski committed 2025-09-01 14:11:18 +03:00
1 parent 7885a05ae1
commit 0e8c28e5f3
1 file changed
+272 -116
+272 -116
View File
@@ -604,9 +604,11 @@
enable() {
this.isEnabled = true;
}
disable() {
this.isEnabled = false;
}
clear() {
this.disable();
this.imageBitmap = null;
@@ -1030,10 +1032,8 @@
await Utils.sleep(100);
}
console.warn(
`❌ Timeout waiting for tiles: ${requiredTiles.length} required,
${requiredTiles.filter((k) => this.originalTiles.has(k)).length} loaded`
);
console.warn(`❌ Timeout waiting for tiles: ${requiredTiles.length} required,
${requiredTiles.filter((k) => this.originalTiles.has(k)).length} loaded`);
return false;
}
}
@@ -1116,26 +1116,56 @@
}
async function handleCaptchaWithRetry() {
const startTime = Date.now();
try {
const sitekey = Utils.detectSitekey();
console.log('🔑 Generating Turnstile token for sitekey:', sitekey);
const startTime = performance.now();
if (typeof window !== 'undefined' && window.navigator) {
console.log('🧭 UA:', window.navigator.userAgent, 'Platform:', window.navigator.platform);
try {
const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken();
if (!sitekey) {
throw new Error('No valid sitekey found');
}
const token = await Utils.generatePaintToken(sitekey);
if (token && token.length > 20) {
const elapsed = Math.round(Date.now() - startTime);
console.log('🔑 Using sitekey:', sitekey);
if (typeof window !== 'undefined' && window.navigator) {
console.log(
'🧭 UA:',
window.navigator.userAgent.substring(0, 50) + '...',
'Platform:',
window.navigator.platform
);
}
let token = null;
if (
preGeneratedToken &&
typeof preGeneratedToken === 'string' &&
preGeneratedToken.length > 20
) {
console.log('♻️ Reusing pre-generated Turnstile token');
token = preGeneratedToken;
} else {
if (isTokenValid()) {
console.log('♻️ Using existing cached token (from previous session)');
token = turnstileToken;
} else {
console.log('🔐 Generating new token with executeTurnstile...');
token = await Utils.executeTurnstile(sitekey, 'paint');
if (token) setTurnstileToken(token);
}
}
if (token && typeof token === 'string' && token.length > 20) {
const elapsed = Math.round(performance.now() - startTime);
console.log(`✅ Turnstile token generated successfully in ${elapsed}ms`);
return token;
} else {
throw new Error('Invalid or empty token received');
throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`);
}
} catch (error) {
const elapsed = Math.round(Date.now() - startTime);
console.log(`❌ Turnstile token generation failed after ${elapsed}ms:`, error);
const elapsed = Math.round(performance.now() - startTime);
console.error(`❌ Turnstile token generation failed after ${elapsed}ms:`, error);
throw error;
}
}
@@ -1178,7 +1208,18 @@
try {
const payload = JSON.parse(args[1].body);
if (payload.t) {
console.log('✅ Turnstile Token Captured:', payload.t);
// 📊 Debug log
console.log(
`🔍✅ Turnstile Token Captured - Type: ${typeof payload.t}, Value: ${
payload.t
? typeof payload.t === 'string'
? payload.t.length > 50
? payload.t.substring(0, 50) + '...'
: payload.t
: JSON.stringify(payload.t)
: 'null/undefined'
}, Length: ${payload.t?.length || 0}`
);
window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*');
}
} catch (_) {
@@ -1279,8 +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
}, // Turnstile Generator Integration - Optimized with widget reuse and proper cleanup
turnstileLoaded: false,
_turnstileContainer: null,
_turnstileOverlay: null,
@@ -1579,10 +1619,6 @@
});
},
async generatePaintToken(sitekey) {
return this.executeTurnstile(sitekey, 'paint');
},
// Cleanup method for when the script is disabled/reloaded
cleanupTurnstile() {
if (this._turnstileWidgetId && window.turnstile?.remove) {
@@ -1607,11 +1643,17 @@
this._lastSitekey = null;
},
detectSitekey(fallback = '0x4AAAAAABpqJe8FO0N84q0F') {
async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') {
// Cache sitekey to avoid repeated DOM queries
if (this._cachedSitekey) {
console.log('🔍 Using cached sitekey:', this._cachedSitekey);
return this._cachedSitekey;
return isTokenValid()
? {
sitekey: this._cachedSitekey,
token: turnstileToken,
}
: { sitekey: this._cachedSitekey, token: null };
}
// List of potential sitekeys to try
@@ -1620,77 +1662,101 @@
'0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey
'0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey
];
const trySitekey = async (sitekey, source) => {
if (!sitekey || sitekey.length < 10) return null;
console.log(`🔍 Testing sitekey from ${source}:`, sitekey);
const token = await this.executeTurnstile(sitekey);
if (token && token.length >= 20) {
console.log(`✅ Valid token generated from ${source} sitekey`);
setTurnstileToken(token);
this._cachedSitekey = sitekey;
return { sitekey, token };
} else {
console.log(`❌ Failed to get token from ${source} sitekey`);
return null;
}
};
try {
// Try to find sitekey in data attributes
// 1️⃣ data-sitekey attribute
const sitekeySel = document.querySelector('[data-sitekey]');
if (sitekeySel) {
const sitekey = sitekeySel.getAttribute('data-sitekey');
if (sitekey && sitekey.length > 10) {
this._cachedSitekey = sitekey;
console.log('🔍 Sitekey detected from data attribute:', sitekey);
return sitekey;
const result = await trySitekey(sitekey, 'data attribute');
if (result) {
return result;
}
}
// Try turnstile element
// 2️⃣ Turnstile element
const turnstileEl = document.querySelector('.cf-turnstile');
if (turnstileEl?.dataset?.sitekey && turnstileEl.dataset.sitekey.length > 10) {
this._cachedSitekey = turnstileEl.dataset.sitekey;
console.log('🔍 Sitekey detected from turnstile element:', this._cachedSitekey);
return this._cachedSitekey;
if (turnstileEl?.dataset?.sitekey) {
const sitekey = turnstileEl.dataset.sitekey;
const result = await trySitekey(sitekey, 'turnstile element');
if (result) {
return result;
}
}
// Try to find sitekey in meta tags
// 3️⃣ Meta tags
const metaTags = document.querySelectorAll(
'meta[name*="turnstile"], meta[property*="turnstile"]'
);
for (const meta of metaTags) {
const content = meta.getAttribute('content');
if (content && content.length > 10) {
this._cachedSitekey = content;
console.log('🔍 Sitekey detected from meta tag:', this._cachedSitekey);
return this._cachedSitekey;
const result = await trySitekey(content, 'meta tag');
if (result) {
return result;
}
}
// Try global variable
if (
typeof window !== 'undefined' &&
window.__TURNSTILE_SITEKEY &&
window.__TURNSTILE_SITEKEY.length > 10
) {
this._cachedSitekey = window.__TURNSTILE_SITEKEY;
console.log('🔍 Sitekey detected from global variable:', this._cachedSitekey);
return this._cachedSitekey;
// 4️⃣ Global variable
if (window.__TURNSTILE_SITEKEY) {
const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable');
if (result) {
return result;
}
}
// Try script tags for inline sitekey
// 5️⃣ Script tags
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
const content = script.textContent || script.innerHTML;
const sitekeyMatch = content.match(/sitekey['":\s]+(['"0-9a-zA-X_-]{20,})/i);
if (sitekeyMatch && sitekeyMatch[1] && sitekeyMatch[1].length > 10) {
this._cachedSitekey = sitekeyMatch[1].replace(/['"]/g, '');
console.log('🔍 Sitekey detected from script content:', this._cachedSitekey);
return this._cachedSitekey;
const match = content.match(
/(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i
);
if (match && match[1]) {
const extracted = match[1].replace(/['"]/g, '');
const result = await trySitekey(extracted, 'script content');
if (result) {
return result;
}
}
}
// If no sitekey found through detection, try the known working sitekeys
console.log('🔍 No sitekey detected, trying known working sitekeys...');
// 6️⃣ Known potential sitekeys
console.log('🔍 Testing known potential sitekeys...');
for (const testSitekey of potentialSitekeys) {
console.log('🔍 Trying sitekey:', testSitekey);
this._cachedSitekey = testSitekey;
return testSitekey;
const result = await trySitekey(testSitekey, 'known list');
if (result) {
return result;
}
}
} catch (error) {
console.warn('Error detecting sitekey:', error);
console.warn('⚠️ Error during sitekey detection:', error);
}
console.log('🔍 Using fallback sitekey:', fallback);
this._cachedSitekey = fallback;
return fallback;
// 7️⃣ Fallback
console.log('🔧 Trying fallback sitekey:', fallback);
const fallbackResult = await trySitekey(fallback, 'fallback');
if (fallbackResult) {
return fallbackResult;
}
console.error('❌ No working sitekey or token found.');
return { sitekey: null, token: null };
},
createElement: (tag, props = {}, children = []) => {
@@ -1892,11 +1958,15 @@
},
resolveColor(targetRgb, availableColors, exactMatch = false) {
if (!availableColors || availableColors.length === 0) return { id: null, rgb: targetRgb };
if (!availableColors || availableColors.length === 0)
return {
id: null,
rgb: targetRgb,
};
const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${
state.colorMatchingAlgorithm
}|${state.enableChromaPenalty ? 'c' : 'nc'}|${state.chromaPenaltyWeight}|${exactMatch ? 'exact' : 'closest'}`;
const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${state.colorMatchingAlgorithm}|${
state.enableChromaPenalty ? 'c' : 'nc'
}|${state.chromaPenaltyWeight}|${exactMatch ? 'exact' : 'closest'}`;
if (colorCache.has(cacheKey)) return colorCache.get(cacheKey);
@@ -2049,7 +2119,10 @@
extractAvailableColors: () => {
const colorElements = document.querySelectorAll('.tooltip button[id^="color-"]');
if (colorElements.length === 0) {
console.log('❌ No color elements found on page');
return null;
}
// Separate available and unavailable colors
const availableColors = [];
const unavailableColors = [];
@@ -2089,7 +2162,9 @@
console.log('\n--- AVAILABLE COLORS ---');
availableColors.forEach((color, index) => {
console.log(
`${index + 1}. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})`
`${
index + 1
}. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})`
);
});
}
@@ -2098,7 +2173,9 @@
console.log('\n--- UNAVAILABLE COLORS ---');
unavailableColors.forEach((color, index) => {
console.log(
`${index + 1}. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]}) [LOCKED]`
`${
index + 1
}. ID: ${color.id}, Name: "${color.name}", RGB: (${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]}) [LOCKED]`
);
});
}
@@ -2993,6 +3070,7 @@
.querySelector('#unselectPaidBtn')
?.addEventListener('click', () => unselectAllPaidColors());
}
async function handleCaptcha() {
const startTime = performance.now();
@@ -3005,7 +3083,12 @@
// Generator mode (pure) or Hybrid mode - try generator first
try {
// Use optimized token generation with automatic sitekey detection
const sitekey = Utils.detectSitekey();
const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken();
if (!sitekey) {
throw new Error('No valid sitekey found');
}
console.log('🔑 Generating Turnstile token for sitekey:', sitekey);
console.log(
'🧭 UA:',
@@ -3019,8 +3102,32 @@
await Utils.loadTurnstile();
}
const token = await Utils.generatePaintToken(sitekey);
let token = null;
// ✅ Reuse pre-generated token if available and valid
if (
preGeneratedToken &&
typeof preGeneratedToken === 'string' &&
preGeneratedToken.length > 20
) {
console.log('♻️ Reusing pre-generated token from sitekey detection phase');
token = preGeneratedToken;
}
// ✅ Or use globally cached token if still valid
else if (isTokenValid()) {
console.log('♻️ Using existing cached token (from previous operation)');
token = turnstileToken;
}
// ✅ Otherwise generate a new one
else {
console.log('🔐 No valid pre-generated or cached token, creating new one...');
token = await Utils.executeTurnstile(sitekey, 'paint');
if (token) {
setTurnstileToken(token);
}
}
// 📊 Debug log
console.log(
`🔍 Token received - Type: ${typeof token}, Value: ${
token
@@ -3033,6 +3140,7 @@
}, Length: ${token?.length || 0}`
);
// ✅ Final validation
if (typeof token === 'string' && token.length > 20) {
const duration = Math.round(performance.now() - startTime);
console.log(`✅ Turnstile token generated successfully in ${duration}ms`);
@@ -3371,7 +3479,9 @@
z-index: 99999;
color: ${theme.text || 'white'};
font-family: ${theme.fontFamily || "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"};
box-shadow: ${theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1)'};
box-shadow: ${
theme.boxShadow || '0 20px 40px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1)'
};
backdrop-filter: ${theme.backdropFilter || 'blur(10px)'};
overflow: hidden;
animation: settingsSlideIn 0.4s ease-out;
@@ -3407,9 +3517,15 @@
</label>
<div class="wplace-settings-section-wrapper">
<select id="tokenSourceSelect" class="wplace-settings-select">
<option value="generator" ${state.tokenSource === 'generator' ? 'selected' : ''} class="wplace-settings-option">🤖 Automatic Token Generator (Recommended)</option>
<option value="hybrid" ${state.tokenSource === 'hybrid' ? 'selected' : ''} class="wplace-settings-option">🔄 Generator + Auto Fallback</option>
<option value="manual" ${state.tokenSource === 'manual' ? 'selected' : ''} class="wplace-settings-option">🎯 Manual Pixel Placement</option>
<option value="generator" ${
state.tokenSource === 'generator' ? 'selected' : ''
} class="wplace-settings-option">🤖 Automatic Token Generator (Recommended)</option>
<option value="hybrid" ${
state.tokenSource === 'hybrid' ? 'selected' : ''
} class="wplace-settings-option">🔄 Generator + Auto Fallback</option>
<option value="manual" ${
state.tokenSource === 'manual' ? 'selected' : ''
} class="wplace-settings-option">🎯 Manual Pixel Placement</option>
</select>
<p class="wplace-settings-description">
Generator mode creates tokens automatically. Hybrid mode falls back to manual when generator fails. Manual mode only uses pixel placement.
@@ -3429,7 +3545,9 @@
<!-- Overlay Settings Section -->
<div class="wplace-settings-section">
<label class="wplace-settings-section-label" style="color: ${theme.text || 'white'};">
<i class="fas fa-eye wplace-icon-eye" style="color: ${theme.highlight || '#48dbfb'};"></i>
<i class="fas fa-eye wplace-icon-eye" style="color: ${
theme.highlight || '#48dbfb'
};"></i>
Overlay Settings
</label>
<div class="wplace-settings-section-wrapper wplace-overlay-wrapper" style="
@@ -3448,7 +3566,9 @@
<!-- Opacity Slider -->
<div class="wplace-overlay-opacity-control">
<div class="wplace-overlay-opacity-header">
<span class="wplace-overlay-opacity-label" style="color: ${theme.text || 'white'};">Overlay Opacity</span>
<span class="wplace-overlay-opacity-label" style="color: ${
theme.text || 'white'
};">Overlay Opacity</span>
<div id="overlayOpacityValue" class="wplace-overlay-opacity-value" style="
background: ${theme.secondary || 'rgba(0,0,0,0.2)'};
color: ${theme.text || 'white'};
@@ -3459,17 +3579,25 @@
">${Math.round(state.overlayOpacity * 100)}%</div>
</div>
<input type="range" id="overlayOpacitySlider" min="0.1" max="1" step="0.05" value="${state.overlayOpacity}" class="wplace-overlay-opacity-slider" style="
background: linear-gradient(to right, ${theme.highlight || '#48dbfb'} 0%, ${theme.purple || theme.neon || '#d3a4ff'} 100%);
background: linear-gradient(to right, ${
theme.highlight || '#48dbfb'
} 0%, ${theme.purple || theme.neon || '#d3a4ff'} 100%);
border-radius: ${theme.borderRadius === '0' ? '0' : '4px'};
">
</div>
<!-- Blue Marble Toggle -->
<label for="enableBlueMarbleToggle" class="wplace-blue-marble-toggle">
<div>
<span class="wplace-blue-marble-title" style="color: ${theme.text || 'white'};">Blue Marble Effect</span>
<p class="wplace-blue-marble-description" style="color: ${theme.text ? `${theme.text}BB` : 'rgba(255,255,255,0.7)'};">Renders a dithered "shredded" overlay.</p>
<span class="wplace-blue-marble-title" style="color: ${
theme.text || 'white'
};">Blue Marble Effect</span>
<p class="wplace-blue-marble-description" style="color: ${
theme.text ? `${theme.text}BB` : 'rgba(255,255,255,0.7)'
};">Renders a dithered "shredded" overlay.</p>
</div>
<input type="checkbox" id="enableBlueMarbleToggle" ${state.blueMarbleEnabled ? 'checked' : ''} class="wplace-blue-marble-checkbox" style="
<input type="checkbox" id="enableBlueMarbleToggle" ${
state.blueMarbleEnabled ? 'checked' : ''
} class="wplace-blue-marble-checkbox" style="
accent-color: ${theme.highlight || '#48dbfb'};
"/>
</label>
@@ -3714,7 +3842,9 @@
${Object.keys(CONFIG.THEMES)
.map(
(themeName) =>
`<option value="${themeName}" ${CONFIG.currentTheme === themeName ? 'selected' : ''} class="wplace-settings-option">${themeName}</option>`
`<option value="${themeName}" ${
CONFIG.currentTheme === themeName ? 'selected' : ''
} class="wplace-settings-option">${themeName}</option>`
)
.join('')}
</select>
@@ -3955,8 +4085,12 @@
<label class="resize-advanced-label">
<span class="resize-advanced-label-text">Algorithm</span>
<select id="colorAlgorithmSelect" class="resize-advanced-select">
<option value="lab" ${state.colorMatchingAlgorithm === 'lab' ? 'selected' : ''}>Perceptual (Lab)</option>
<option value="legacy" ${state.colorMatchingAlgorithm === 'legacy' ? 'selected' : ''}>Legacy (RGB)</option>
<option value="lab" ${
state.colorMatchingAlgorithm === 'lab' ? 'selected' : ''
}>Perceptual (Lab)</option>
<option value="legacy" ${
state.colorMatchingAlgorithm === 'legacy' ? 'selected' : ''
}>Legacy (RGB)</option>
</select>
</label>
<label class="resize-advanced-toggle">
@@ -3964,7 +4098,9 @@
<span class="resize-advanced-label-text">Chroma Penalty</span>
<div class="resize-advanced-description">Preserve vivid colors (Lab only)</div>
</div>
<input type="checkbox" id="enableChromaPenaltyToggle" ${state.enableChromaPenalty ? 'checked' : ''} class="resize-advanced-checkbox" />
<input type="checkbox" id="enableChromaPenaltyToggle" ${
state.enableChromaPenalty ? 'checked' : ''
} class="resize-advanced-checkbox" />
</label>
<div class="resize-chroma-weight-control">
<div class="resize-chroma-weight-header">
@@ -3978,7 +4114,9 @@
<span class="resize-advanced-label-text">Enable Dithering</span>
<div class="resize-advanced-description">Floyd–Steinberg error diffusion in preview and applied output</div>
</div>
<input type="checkbox" id="enableDitheringToggle" ${state.ditheringEnabled ? 'checked' : ''} class="resize-advanced-checkbox" />
<input type="checkbox" id="enableDitheringToggle" ${
state.ditheringEnabled ? 'checked' : ''
} class="resize-advanced-checkbox" />
</label>
<div class="resize-threshold-controls">
<label class="resize-threshold-label">
@@ -4795,8 +4933,10 @@
state.currentCharges = Math.floor(currentCharges);
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>`;
if (chargesEl && (secondsInMinute === 0 || secondsInMinute === 30)) {
chargesEl.innerHTML = `<span">${Math.floor(
state.currentCharges
)} / ${state.maxCharges}</span>`;
}
if (currentCharges >= max) {
fullChargeEl.innerHTML = `<span style="color:#10b981;">FULL</span>`;
@@ -4865,21 +5005,21 @@
}
let colorSwatchesHTML = '';
const availableColors = Utils.extractAvailableColors();
if (availableColors.length < 10 && isManualRefresh) {
Utils.showAlert(Utils.t('noColorsFound'), 'warning');
}
state.availableColors = state.availableColors.filter(
(c) => c.name !== 'Unknown CoIor NaN' && c.id !== null
);
if (state.availableColors.length < availableColors.length) {
const availableColors = Utils.extractAvailableColors();
const newCount = Array.isArray(availableColors) ? availableColors.length : 0;
if (newCount === 0 && isManualRefresh) {
Utils.showAlert(Utils.t('noColorsFound'), 'warning');
} else if (newCount > 0 && state.availableColors.length < newCount) {
const oldCount = state.availableColors.length;
const newCount = availableColors.length;
Utils.showAlert(
Utils.t('colorsUpdated', {
oldCount: oldCount,
oldCount,
newCount: newCount,
diffCount: newCount - oldCount,
}),
@@ -6053,7 +6193,7 @@
if (uploadBtn) {
uploadBtn.addEventListener('click', async () => {
const availableColors = Utils.extractAvailableColors();
if (availableColors.length < 10) {
if (availableColors === null || availableColors.length < 10) {
updateUI('noColorsFound', 'error');
Utils.showAlert(Utils.t('noColorsFound'), 'error');
return;
@@ -6595,7 +6735,12 @@
}
let pixelBatch = null;
let skippedPixels = { transparent: 0, white: 0, alreadyPainted: 0, colorUnavailable: 0 };
let skippedPixels = {
transparent: 0,
white: 0,
alreadyPainted: 0,
colorUnavailable: 0,
};
const transparencyThreshold =
state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
@@ -6609,9 +6754,15 @@
a = pixels[idx + 3];
if (!state.paintTransparentPixels && a < transparencyThreshold)
return { eligible: false, reason: 'transparent' };
return {
eligible: false,
reason: 'transparent',
};
if (!state.paintWhitePixels && Utils.isWhitePixel(r, g, b))
return { eligible: false, reason: 'white' };
return {
eligible: false,
reason: 'white',
};
let targetRgb = Utils.isWhitePixel(r, g, b)
? [255, 255, 255]
@@ -6623,7 +6774,15 @@
);
if (strictSkipUnavailable && !colorCheck.id)
return { eligible: false, reason: 'colorUnavailable', r, g, b, a, colorId: colorCheck.id };
return {
eligible: false,
reason: 'colorUnavailable',
r,
g,
b,
a,
colorId: colorCheck.id,
};
return { eligible: true, r, g, b, a, colorId: colorCheck.id };
}
@@ -6693,9 +6852,9 @@
) {
if (pixelBatch && pixelBatch.pixels.length > 0) {
console.log(
`🌍 Sending region-change batch with ${
pixelBatch.pixels.length
} pixels (switching to region ${regionX + adderX},${regionY + adderY})`
`🌍 Sending region-change batch with ${pixelBatch.pixels.length} pixels (switching to region ${
regionX + adderX
},${regionY + adderY})`
);
const success = await flushPixelBatch(pixelBatch);
@@ -6739,13 +6898,6 @@
const [er, eg, eb] = existingColorRGBA;
const existingColor = Utils.resolveColor([er, eg, eb], state.availableColors);
const isMatch = existingColor.id === targetColorId;
console.debug(
`[COMPARE] Pixel at (${pixelX}, ${pixelY})\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` +
` 📍 Absolute position: (${startX + x}, ${startY + y}) in region (${regionX + adderX}, ${regionY + adderY})`
);
if (isMatch) {
skipPixel(
'alreadyPainted',
@@ -6756,6 +6908,12 @@
);
continue;
}
console.debug(
`[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`
);
}
} catch (e) {
console.error(`[DEBUG] Error checking existing pixel at (${pixelX}, ${pixelY}):`, e);
@@ -7052,8 +7210,7 @@
coordinateDirection: state.coordinateDirection,
coordinateSnake: state.coordinateSnake,
blockWidth: state.blockWidth,
blockHeight: state.blockHeight,
// Save ignore mask (as base64) with its dimensions
blockHeight: state.blockHeight, // Save ignore mask (as base64) with its dimensions
resizeIgnoreMask:
state.resizeIgnoreMask &&
state.resizeSettings &&
@@ -7063,8 +7220,7 @@
h: state.resizeSettings.height,
data: btoa(String.fromCharCode(...state.resizeIgnoreMask)),
}
: null,
// Notifications
: null, // Notifications
notificationsEnabled: state.notificationsEnabled,
notifyOnChargesReached: state.notifyOnChargesReached,
notifyOnlyWhenUnfocused: state.notifyOnlyWhenUnfocused,