| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- const snapshotBtn = document.getElementById('snapshotBtn');
- const snapshotList = document.getElementById('snapshotList');
- const snapshotComment = document.getElementById('snapshotComment');
- snapshotBtn.addEventListener('click', async() => {
- const snapshot = await takeSnapshot();
- const res = await fetch('/api/snapshots', {
- method: 'POST',
- headers: {'Content-Type': 'text/plain'},
- body: JSON.stringify(snapshot, jsonReplacer)
- });
- if(res.ok) {
- alert('Snapshot saved successfully!');
- loadSnapshots();
- } else {
- alert('Something went wrong while saving the snapshot: ' + res.statusText);
- }
- });
- snapshotComment.innerHTML = `Taken on ${new Date().toDateString()}`;
- async function takeSnapshot() {
- const localStorageData = {};
- for(let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- localStorageData[key] = localStorage.getItem(key);
- }
- const indexedDBData = {};
- const dbs = await indexedDB.databases?.() || [];
- for(const {name} of dbs) {
- if(!name) continue;
- indexedDBData[name] = {};
- const db = await openDB(name);
- for(const storeName of db.objectStoreNames) {
- const tx = db.transaction(storeName, 'readonly');
- const store = tx.objectStore(storeName);
- const entries = await getAllEntries(store);
- indexedDBData[name][storeName] = entries;
- }
- db.close();
- }
- return {
- comment: snapshotComment.innerHTML,
- localStorage: localStorageData,
- indexedDB: indexedDBData
- };
- }
- function openDB(name) {
- return new Promise((resolve, reject) => {
- const req = indexedDB.open(name);
- req.onsuccess = () => resolve(req.result);
- req.onerror = () => reject(req.error);
- });
- }
- // function getAllRecords(store) {
- // return new Promise((resolve, reject) => {
- // const request = store.getAll();
- // request.onsuccess = () => resolve(request.result);
- // request.onerror = () => reject(request.error);
- // });
- // }
- async function loadSnapshots() {
- const res = await fetch('/api/snapshots');
- const snapshots = await res.json();
- snapshotList.innerHTML = '';
- snapshots.forEach(({name, comment}) => {
- const li = document.createElement('div');
- li.classList.add('snapshot-item')
- const nameEl = document.createElement('span');
- nameEl.textContent = name;
- nameEl.classList.add('snapshot-name');
- const commentEl = document.createElement('div');
- commentEl.innerHTML = comment;
- commentEl.classList.add('snapshot-item-comment');
- const loadBtn = document.createElement('button');
- loadBtn.textContent = 'Load';
- loadBtn.onclick = () => loadSnapshot(name);
- const deleteBtn = document.createElement('button');
- deleteBtn.textContent = 'Delete';
- deleteBtn.onclick = async() => {
- const confirmed = confirm('Are you sure you want to delete this snapshot?');
- if(!confirmed) return;
- await fetch(`/api/snapshots/${name}`, {method: 'DELETE'});
- alert('Snapshot successfully deleted');
- loadSnapshots();
- };
- li.appendChild(nameEl)
- li.appendChild(loadBtn);
- li.appendChild(deleteBtn);
- snapshotList.appendChild(li);
- if((comment || '').trim?.()) snapshotList.appendChild(commentEl);
- });
- }
- window.onload = loadSnapshots;
- async function loadSnapshot(filename) {
- if(!confirm(`Are you sure you want to load snapshot "${filename}"? This will overwrite your current storage.`)) {
- return;
- }
- const res = await fetch(`/api/snapshots/${filename}`);
- if(!res.ok) {
- alert('Failed to load snapshot');
- return;
- }
- const snapshotText = await res.text();
- const snapshot = JSON.parse(snapshotText, jsonReviver);
- // Restore localStorage
- localStorage.clear();
- for(const [key, value] of Object.entries(snapshot.localStorage || {})) {
- localStorage.setItem(key, value);
- }
- // Restore indexedDB
- await clearAllIndexedDB();
- for(const [dbName, stores] of Object.entries(snapshot.indexedDB || {})) {
- await restoreIndexedDB(dbName, stores);
- }
- alert('Snapshot loaded successfully!');
- }
- function clearAllIndexedDB() {
- return new Promise(async(resolve) => {
- const dbs = await indexedDB.databases?.() || [];
- let count = dbs.length;
- if(count === 0) resolve();
- for(const {name} of dbs) {
- if(!name) {
- count--;
- if(count === 0) resolve();
- continue;
- }
- const req = indexedDB.deleteDatabase(name);
- req.onsuccess = () => {
- count--;
- if(count === 0) resolve();
- };
- req.onerror = () => {
- console.warn(`Failed to delete indexedDB ${name}`);
- count--;
- if(count === 0) resolve();
- };
- }
- });
- }
- function restoreIndexedDB(dbName, stores) {
- return new Promise((resolve, reject) => {
- const req = indexedDB.open(dbName);
- req.onupgradeneeded = (event) => {
- const db = event.target.result;
- // Delete existing object stores if any
- Array.from(db.objectStoreNames).forEach(storeName => {
- db.deleteObjectStore(storeName);
- });
- // Create stores from snapshot
- for(const storeName of Object.keys(stores)) {
- db.createObjectStore(storeName/* , {keyPath: 'id', autoIncrement: false} */);
- }
- };
- req.onsuccess = async(event) => {
- const db = event.target.result;
- // Write all entries to each store
- try {
- for(const [storeName, entries] of Object.entries(stores)) {
- const tx = db.transaction(storeName, 'readwrite');
- const store = tx.objectStore(storeName);
- for(const entry of entries) {
- store.put(entry.value, entry.key);
- }
- await tx.complete; // some browsers support promise on tx.complete, others don't
- }
- } catch(e) {
- console.error('Error restoring indexedDB data', e);
- }
- db.close();
- resolve();
- };
- req.onerror = () => reject(req.error);
- });
- }
- function jsonReplacer(key, value) {
- if(value instanceof Uint8Array) {
- return {
- __type: 'Uint8Array',
- data: Array.from(value) // convert to normal array for JSON
- };
- }
- return value;
- }
- function jsonReviver(key, value) {
- if(value && value.__type === 'Uint8Array' && Array.isArray(value.data)) {
- return new Uint8Array(value.data);
- }
- return value;
- }
- function getAllEntries(store) {
- return new Promise((resolve, reject) => {
- const entries = [];
- const request = store.openCursor();
- request.onsuccess = (event) => {
- const cursor = event.target.result;
- if(cursor) {
- entries.push({key: cursor.key, value: cursor.value});
- cursor.continue();
- } else {
- resolve(entries);
- }
- };
- request.onerror = () => reject(request.error);
- });
- }
|