mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-13 20:14:24 +00:00
Add IndexedDB support for large image data storage; implement async pixel loading and fallback handling.
This commit is contained in:
1 parent
c04d698951
commit
e4c9fd3f3a
3 files changed
+225
-20
No files matched your search
@@ -2526,6 +2526,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Decide how to store image pixels (inline for small images, IndexedDB ref for large)
|
||||
let imageDataForSave = null;
|
||||
if (state.imageData) {
|
||||
const width = state.imageData.width;
|
||||
const height = state.imageData.height;
|
||||
const totalPixels = state.imageData.totalPixels;
|
||||
const pixels = state.imageData.pixels;
|
||||
const USE_IDB_THRESHOLD = 500000; // ~0.5 MB raw RGBA
|
||||
|
||||
if (pixels && pixels.length > USE_IDB_THRESHOLD && window.globalUtilsManager && typeof window.globalUtilsManager.storePixelsInIndexedDB === 'function' && typeof window.globalUtilsManager.generatePixelsRef === 'function') {
|
||||
const ref = window.globalUtilsManager.generatePixelsRef(width, height);
|
||||
try { window.globalUtilsManager.storePixelsInIndexedDB(ref, { width, height, totalPixels, pixels }); } catch (e) { console.warn('IDB store failed:', e); }
|
||||
imageDataForSave = { width, height, totalPixels, pixelsRef: ref, pixelsBackend: 'idb' };
|
||||
} else if (pixels) {
|
||||
imageDataForSave = { width, height, pixels: Array.from(pixels), totalPixels };
|
||||
} else {
|
||||
imageDataForSave = { width, height, totalPixels, pixels: null };
|
||||
}
|
||||
}
|
||||
|
||||
const progressData = {
|
||||
timestamp: Date.now(),
|
||||
version: "2.1",
|
||||
@@ -2539,14 +2559,7 @@
|
||||
colorsChecked: state.colorsChecked,
|
||||
availableColors: state.availableColors,
|
||||
},
|
||||
imageData: state.imageData
|
||||
? {
|
||||
width: state.imageData.width,
|
||||
height: state.imageData.height,
|
||||
pixels: Array.from(state.imageData.pixels),
|
||||
totalPixels: state.imageData.totalPixels,
|
||||
}
|
||||
: null,
|
||||
imageData: imageDataForSave,
|
||||
paintedMapPacked: paintedMapPacked,
|
||||
}
|
||||
|
||||
@@ -2653,6 +2666,36 @@
|
||||
console.warn('Could not rebuild processor from saved image data:', e);
|
||||
}
|
||||
}
|
||||
else if (savedData.imageData && savedData.imageData.pixelsRef) {
|
||||
console.warn('ℹ️ Large image detected; loading pixels from IndexedDB via ref:', savedData.imageData.pixelsRef);
|
||||
state.imageData = {
|
||||
width: savedData.imageData.width,
|
||||
height: savedData.imageData.height,
|
||||
totalPixels: savedData.imageData.totalPixels,
|
||||
pixels: null,
|
||||
};
|
||||
state.imageLoaded = false;
|
||||
|
||||
if (window.globalUtilsManager && typeof window.globalUtilsManager.loadPixelsFromIndexedDB === 'function') {
|
||||
window.globalUtilsManager.loadPixelsFromIndexedDB(savedData.imageData.pixelsRef)
|
||||
.then(payload => {
|
||||
if (!payload || !payload.pixels) {
|
||||
console.warn('⚠️ Pixels not found in IDB. Ask user to reload image.');
|
||||
return;
|
||||
}
|
||||
state.imageData.pixels = payload.pixels instanceof Uint8Array || payload.pixels instanceof Uint8ClampedArray
|
||||
? new Uint8ClampedArray(payload.pixels)
|
||||
: new Uint8ClampedArray(payload.pixels);
|
||||
state.imageLoaded = true;
|
||||
|
||||
// Optionally try to restore overlay here if available in this script context
|
||||
if (window.globalUtilsManager && typeof window.globalUtilsManager.restoreOverlayFromData === 'function') {
|
||||
window.globalUtilsManager.restoreOverlayFromData().catch(() => {});
|
||||
}
|
||||
})
|
||||
.catch(err => console.warn('❌ Failed to load pixels from IDB:', err));
|
||||
}
|
||||
}
|
||||
else if (savedData.imageData && savedData.imageData.pixelsStripped) {
|
||||
console.warn('⚠️ Saved progress did not include raw pixel data due to quota limits. Please reload the image to resume.');
|
||||
state.imageData = {
|
||||
|
||||
@@ -1474,6 +1474,36 @@ function getText(key, params) {
|
||||
};
|
||||
console.log('✅ [DEBUG] ImageData restored successfully');
|
||||
console.log('🔍 [DEBUG] Converted pixels to Uint8ClampedArray, length:', window.state.imageData.pixels.length);
|
||||
} else if (savedData.imageData && savedData.imageData.pixelsRef) {
|
||||
console.warn('ℹ️ [DEBUG] Large image detected; loading pixels from IndexedDB via ref:', savedData.imageData.pixelsRef);
|
||||
window.state.imageData = {
|
||||
width: savedData.imageData.width,
|
||||
height: savedData.imageData.height,
|
||||
totalPixels: savedData.imageData.totalPixels,
|
||||
pixels: null,
|
||||
};
|
||||
window.state.imageLoaded = false;
|
||||
|
||||
if (window.globalUtilsManager && typeof window.globalUtilsManager.loadPixelsFromIndexedDB === 'function') {
|
||||
window.globalUtilsManager.loadPixelsFromIndexedDB(savedData.imageData.pixelsRef)
|
||||
.then(payload => {
|
||||
if (!payload || !payload.pixels) {
|
||||
console.warn('⚠️ [DEBUG] Pixels not found in IDB. Ask user to reload image.');
|
||||
return;
|
||||
}
|
||||
window.state.imageData.pixels = payload.pixels instanceof Uint8Array || payload.pixels instanceof Uint8ClampedArray
|
||||
? new Uint8ClampedArray(payload.pixels)
|
||||
: new Uint8ClampedArray(payload.pixels);
|
||||
window.state.imageLoaded = true;
|
||||
console.log('✅ [DEBUG] Pixels loaded from IDB');
|
||||
|
||||
// Try to restore overlay immediately
|
||||
if (typeof window.globalUtilsManager.restoreOverlayFromData === 'function') {
|
||||
window.globalUtilsManager.restoreOverlayFromData().catch(() => {});
|
||||
}
|
||||
})
|
||||
.catch(err => console.warn('❌ [DEBUG] Failed to load pixels from IDB:', err));
|
||||
}
|
||||
} else if (savedData.imageData && savedData.imageData.pixelsStripped) {
|
||||
console.warn('⚠️ [DEBUG] Saved progress did not include raw pixel data due to quota limits. Please reload the image file to resume.');
|
||||
// Preserve dimensions for UI, but mark image as not loaded
|
||||
@@ -8269,7 +8299,7 @@ function getText(key, params) {
|
||||
}
|
||||
|
||||
function generateCoordinates(width, height, mode, direction, snake, blockWidth, blockHeight, startFromX = 0, startFromY = 0) {
|
||||
const coords = [];
|
||||
let coords = [];
|
||||
console.log(
|
||||
'Generating coordinates with \n mode:',
|
||||
mode,
|
||||
|
||||
@@ -673,7 +673,7 @@ class WPlaceUtilsManager {
|
||||
}
|
||||
|
||||
buildProgressData() {
|
||||
return {
|
||||
const result = {
|
||||
timestamp: Date.now(),
|
||||
version: '2.2',
|
||||
state: {
|
||||
@@ -691,16 +691,42 @@ class WPlaceUtilsManager {
|
||||
blockHeight: window.state.blockHeight,
|
||||
availableColors: window.state.availableColors,
|
||||
},
|
||||
imageData: window.state.imageData
|
||||
? {
|
||||
width: window.state.imageData.width,
|
||||
height: window.state.imageData.height,
|
||||
pixels: Array.from(window.state.imageData.pixels),
|
||||
totalPixels: window.state.imageData.totalPixels,
|
||||
}
|
||||
: null,
|
||||
paintedMapPacked: this.buildPaintedMapPacked(),
|
||||
imageData: null,
|
||||
paintedMapPacked: null,
|
||||
};
|
||||
|
||||
// Decide how to store image pixels
|
||||
if (window.state.imageData) {
|
||||
const img = window.state.imageData;
|
||||
const width = img.width;
|
||||
const height = img.height;
|
||||
const totalPixels = img.totalPixels;
|
||||
const pixels = img.pixels;
|
||||
|
||||
const USE_IDB_THRESHOLD = 500000; // ~0.5 MB raw RGBA (before JSON expansion)
|
||||
|
||||
if (pixels && pixels.length) {
|
||||
const bytes = pixels.length; // Uint8ClampedArray length equals byte size
|
||||
if (bytes > USE_IDB_THRESHOLD) {
|
||||
// Store pixels in IndexedDB and keep only the reference in save payload
|
||||
const ref = this.generatePixelsRef(width, height);
|
||||
try {
|
||||
// Fire-and-forget async persist
|
||||
this.storePixelsInIndexedDB(ref, { width, height, totalPixels, pixels });
|
||||
} catch (e) {
|
||||
console.warn('Failed to schedule IDB pixel store:', e);
|
||||
}
|
||||
result.imageData = { width, height, totalPixels, pixelsRef: ref, pixelsBackend: 'idb' };
|
||||
} else {
|
||||
result.imageData = { width, height, pixels: Array.from(pixels), totalPixels };
|
||||
}
|
||||
} else {
|
||||
result.imageData = { width, height, totalPixels, pixels: null };
|
||||
}
|
||||
}
|
||||
|
||||
result.paintedMapPacked = this.buildPaintedMapPacked();
|
||||
return result;
|
||||
}
|
||||
|
||||
migrateProgress(saved) {
|
||||
@@ -865,6 +891,45 @@ class WPlaceUtilsManager {
|
||||
} catch (e) {
|
||||
console.warn('Could not rebuild processor from saved image data:', e);
|
||||
}
|
||||
} else if (savedData.imageData && savedData.imageData.pixelsRef) {
|
||||
// Asynchronously load large pixel data from IndexedDB using the stored reference
|
||||
console.log('🔄 Loading large image pixels from IndexedDB via ref:', savedData.imageData.pixelsRef);
|
||||
window.state.imageData = {
|
||||
width: savedData.imageData.width,
|
||||
height: savedData.imageData.height,
|
||||
totalPixels: savedData.imageData.totalPixels,
|
||||
pixels: null,
|
||||
};
|
||||
window.state.imageLoaded = false;
|
||||
|
||||
this.loadPixelsFromIndexedDB(savedData.imageData.pixelsRef)
|
||||
.then((payload) => {
|
||||
if (!payload || !payload.pixels) {
|
||||
console.warn('⚠️ No pixel payload found in IndexedDB for ref:', savedData.imageData.pixelsRef);
|
||||
this.showAlert?.('⚠️ Saved image pixels not found in local database. Please reload the image file.', 'warning');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.state.imageData.pixels = payload.pixels instanceof Uint8Array || payload.pixels instanceof Uint8ClampedArray
|
||||
? new Uint8ClampedArray(payload.pixels)
|
||||
: new Uint8ClampedArray(payload.pixels);
|
||||
window.state.imageLoaded = true;
|
||||
|
||||
// Rebuild processor and notify modules/UI
|
||||
this._syncModulesAfterStateRestore();
|
||||
this.showAlert?.('✅ Large image restored from local database.', 'success');
|
||||
|
||||
// Attempt overlay restoration if available
|
||||
if (typeof this.restoreOverlayFromData === 'function') {
|
||||
this.restoreOverlayFromData().catch(() => {});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to apply pixels from IndexedDB:', err);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('Failed to load pixels from IndexedDB:', err);
|
||||
});
|
||||
} else if (savedData.imageData && savedData.imageData.pixelsStripped) {
|
||||
console.warn('⚠️ Saved progress did not include raw pixel data due to quota limits. Please reload the image to resume.');
|
||||
window.state.imageData = {
|
||||
@@ -1293,4 +1358,71 @@ if (!window.globalUtilsManager) {
|
||||
console.error('❌ Failed to reinitialize WPlaceUtilsManager:', error);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// === IndexedDB helpers for large pixel storage ===
|
||||
WPlaceUtilsManager.prototype._openPixelDB = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const request = indexedDB.open('wplace-bot-db', 1);
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains('pixels')) {
|
||||
const store = db.createObjectStore('pixels', { keyPath: 'ref' });
|
||||
try { store.createIndex('createdAt', 'createdAt', { unique: false }); } catch {}
|
||||
}
|
||||
};
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
WPlaceUtilsManager.prototype.generatePixelsRef = function(width, height) {
|
||||
const rand = Math.random().toString(36).slice(2, 8);
|
||||
return `img_${width}x${height}_${Date.now().toString(36)}_${rand}`;
|
||||
};
|
||||
|
||||
WPlaceUtilsManager.prototype.storePixelsInIndexedDB = async function(ref, payload) {
|
||||
try {
|
||||
const db = await this._openPixelDB();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction('pixels', 'readwrite');
|
||||
const store = tx.objectStore('pixels');
|
||||
const data = {
|
||||
ref,
|
||||
width: payload.width,
|
||||
height: payload.height,
|
||||
totalPixels: payload.totalPixels,
|
||||
pixels: payload.pixels instanceof Uint8Array || payload.pixels instanceof Uint8ClampedArray
|
||||
? new Uint8Array(payload.pixels)
|
||||
: new Uint8Array(payload.pixels),
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
const req = store.put(data);
|
||||
req.onsuccess = () => resolve(true);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('IDB storePixels failed:', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
WPlaceUtilsManager.prototype.loadPixelsFromIndexedDB = async function(ref) {
|
||||
try {
|
||||
const db = await this._openPixelDB();
|
||||
return await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction('pixels', 'readonly');
|
||||
const store = tx.objectStore('pixels');
|
||||
const req = store.get(ref);
|
||||
req.onsuccess = () => resolve(req.result || null);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('IDB loadPixels failed:', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user