Files
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

70 lines
3.0 KiB
JavaScript

/* Origin-local persistence only. Documents and pixel serialization live in Go.
* Never evict another unsaved drawing to make room: quota failures are visible. */
'use strict';
class AegoRecoveryStore {
static MAX_BYTES = 128 * 1024 * 1024;
static MAX_RECORDS = 128;
constructor(db) { this.db = db; }
static open() {
return new Promise((resolve, reject) => {
const req = indexedDB.open('aego-artstudio', 2);
req.onupgradeneeded = () => {
if (!req.result.objectStoreNames.contains('documents')) req.result.createObjectStore('documents', { keyPath: 'key' });
if (req.result.objectStoreNames.contains('recovery')) {
const old = req.transaction.objectStore('recovery').get('current');
old.onsuccess = () => {
if (old.result?.dirty && old.result?.bytes) req.transaction.objectStore('documents').put({ ...old.result, key: 'legacy-current' });
};
}
};
req.onerror = () => reject(req.error);
req.onblocked = () => reject(new Error('Close other Asset Studio windows to upgrade local recovery.'));
req.onsuccess = () => {
const db = req.result;
db.onversionchange = () => db.close();
resolve(new AegoRecoveryStore(db));
};
});
}
list() {
return new Promise((resolve, reject) => {
const tx = this.db.transaction('documents', 'readonly');
const req = tx.objectStore('documents').getAll();
tx.oncomplete = () => resolve(req.result.sort((a, b) => b.at - a.at));
tx.onerror = tx.onabort = () => reject(tx.error || new Error('Recovery read aborted.'));
});
}
put(record) {
if (!record?.key || !(record.bytes instanceof Uint8Array) || record.bytes.byteLength > 64 * 1024 * 1024) {
return Promise.reject(new Error('Invalid recovery record.'));
}
return new Promise((resolve, reject) => {
const tx = this.db.transaction('documents', 'readwrite');
const store = tx.objectStore('documents');
let failure;
const all = store.getAll();
all.onsuccess = () => {
const others = all.result.filter(v => v.key !== record.key);
const bytes = others.reduce((n, v) => n + (v.bytes?.byteLength || 0), record.bytes.byteLength);
if (bytes > AegoRecoveryStore.MAX_BYTES || others.length >= AegoRecoveryStore.MAX_RECORDS) {
failure = new Error('Recovery storage limit reached; save or explicitly discard older drawings.');
tx.abort();
return;
}
store.put(record);
};
tx.oncomplete = resolve;
tx.onerror = tx.onabort = () => reject(failure || tx.error || new Error('Recovery write aborted.'));
});
}
remove(key) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction('documents', 'readwrite');
tx.objectStore('documents').delete(key);
tx.oncomplete = resolve;
tx.onerror = tx.onabort = () => reject(tx.error || new Error('Recovery deletion aborted.'));
});
}
}
window.AegoRecoveryStore = AegoRecoveryStore;