1// IndexedDB-backed gallery storage for Flickgame projects. 2(function () { 3 'use strict'; 4 5 var DB_NAME = 'flickgame'; 6 var DB_VERSION = 1; 7 var STORE_PROJECTS = 'projects'; 8 var dbPromise = null; 9 10 function nowMs() { return Date.now(); } 11 12 function uuid() { 13 if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID(); 14 // Fallback UUIDv4-ish 15 var s = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 16 var r = Math.random() * 16 | 0; 17 var v = c === 'x' ? r : (r & 0x3 | 0x8); 18 return v.toString(16); 19 }); 20 return s; 21 } 22 23 function openDb() { 24 if (dbPromise) return dbPromise; 25 dbPromise = new Promise(function (resolve, reject) { 26 if (typeof indexedDB === 'undefined') { 27 reject(new Error('IndexedDB not available')); 28 return; 29 } 30 var req = indexedDB.open(DB_NAME, DB_VERSION); 31 req.onerror = function () { reject(req.error || new Error('Failed to open IndexedDB')); }; 32 req.onupgradeneeded = function () { 33 var db = req.result; 34 if (!db.objectStoreNames.contains(STORE_PROJECTS)) { 35 var store = db.createObjectStore(STORE_PROJECTS, { keyPath: 'id' }); 36 store.createIndex('updatedAt', 'updatedAt', { unique: false }); 37 store.createIndex('createdAt', 'createdAt', { unique: false }); 38 } 39 }; 40 req.onsuccess = function () { resolve(req.result); }; 41 }); 42 return dbPromise; 43 } 44 45 function withStore(mode, fn) { 46 return openDb().then(function (db) { 47 return new Promise(function (resolve, reject) { 48 var tx = db.transaction(STORE_PROJECTS, mode); 49 var store = tx.objectStore(STORE_PROJECTS); 50 var result; 51 try { result = fn(store, tx); } catch (e) { reject(e); return; } 52 53 var txDone = false; 54 var txError = null; 55 tx.oncomplete = function () { txDone = true; maybeResolve(); }; 56 tx.onerror = function () { reject(tx.error || new Error('IndexedDB transaction failed')); }; 57 tx.onabort = function () { reject(tx.error || new Error('IndexedDB transaction aborted')); }; 58 59 var resultPromise = Promise.resolve(result); 60 var resultDone = false; 61 var resultValue; 62 resultPromise.then(function (v) { resultDone = true; resultValue = v; maybeResolve(); }, function (e) { 63 // If the caller's request promise fails, surface that error (tx may still abort too). 64 txError = e || new Error('IndexedDB request failed'); 65 reject(txError); 66 }); 67 68 function maybeResolve() { 69 if (!txDone) return; 70 if (txError) return; 71 if (!resultDone) return; 72 resolve(resultValue); 73 } 74 }); 75 }); 76 } 77 78 function reqToPromise(req) { 79 return new Promise(function (resolve, reject) { 80 req.onsuccess = function () { resolve(req.result); }; 81 req.onerror = function () { reject(req.error || new Error('IndexedDB request failed')); }; 82 }); 83 } 84 85 function listProjects() { 86 return openDb().then(function (db) { 87 return new Promise(function (resolve, reject) { 88 var tx = db.transaction(STORE_PROJECTS, 'readonly'); 89 var store = tx.objectStore(STORE_PROJECTS); 90 var idx = store.index('updatedAt'); 91 var out = []; 92 var cursorReq = idx.openCursor(null, 'prev'); 93 cursorReq.onerror = function () { reject(cursorReq.error || new Error('Failed to iterate projects')); }; 94 cursorReq.onsuccess = function () { 95 var cur = cursorReq.result; 96 if (!cur) { resolve(out); return; } 97 out.push(cur.value); 98 cur.continue(); 99 }; 100 }); 101 }); 102 } 103 104 function getProject(id) { 105 return withStore('readonly', function (store) { 106 return reqToPromise(store.get(id)); 107 }); 108 } 109 110 function putProject(project) { 111 var p = Object.assign({}, project); 112 if (!p.id) p.id = uuid(); 113 if (!p.title) p.title = 'Untitled'; 114 if (!p.createdAt) p.createdAt = nowMs(); 115 p.updatedAt = nowMs(); 116 return withStore('readwrite', function (store) { 117 return reqToPromise(store.put(p)).then(function () { return p; }); 118 }); 119 } 120 121 function deleteProject(id) { 122 return withStore('readwrite', function (store) { 123 return reqToPromise(store.delete(id)).then(function () { return true; }); 124 }); 125 } 126 127 function isEmpty() { 128 return openDb().then(function (db) { 129 return new Promise(function (resolve, reject) { 130 var tx = db.transaction(STORE_PROJECTS, 'readonly'); 131 var store = tx.objectStore(STORE_PROJECTS); 132 var countReq = store.count(); 133 countReq.onerror = function () { reject(countReq.error || new Error('Failed to count projects')); }; 134 countReq.onsuccess = function () { resolve((countReq.result || 0) === 0); }; 135 }); 136 }); 137 } 138 139 window.FlickGalleryStore = { 140 openDb: openDb, 141 listProjects: listProjects, 142 getProject: getProject, 143 putProject: putProject, 144 deleteProject: deleteProject, 145 isEmpty: isEmpty, 146 _uuid: uuid 147 }; 148})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.