diff --git a/src/add/array.js b/src/add/array.js index 29bfd3d6..0ab16f58 100644 --- a/src/add/array.js +++ b/src/add/array.js @@ -2,258 +2,236 @@ "use strict"; +// extend Array, String, Number, Math (function() { - gapp.register('add.array'); +gapp.register('add.array'); - let AP = {}; +let AP = {}; - /** ****************************************************************** - * Array prototype helpers - ******************************************************************* */ +if (!AP.flat) { + AP.flat = function() { + return [].concat.apply([], this); + }; +} - if (!AP.flat) { - AP.flat = function() { - return [].concat.apply([], this); - }; - } +AP.equals = function(arr) { + if (!arr) return false; + if (arr.length !== this.length) return false; + for (let i = 0; i < this.length; i++) { + if (arr[i] !== this[i]) return false; + } + return true; +}; - AP.equals = function(arr) { - if (!arr) return false; - if (arr.length !== this.length) return false; - for (let i=0; i 0) { + // avoid hitting stack limits + if (arr.length > 10000) { + for (let i = 0, il = arr.length; i < il; i++) { + this.push(arr[i]); + } } else { - this.push(v); - return this; + // this.push.apply(this,arr); (slower?) + this.push(...arr); } - }; + } + return this; +}; - /** - * append all array elements to this array - * - * @param {Array} arr - * @returns {Array} - */ - AP.appendAll = function(arr) { - if (arr && arr.length > 0) { - // avoid hitting stack limits - if (arr.length > 10000) { - for (let i=0, il=arr.length; i { + if (el.xout) { + el.xout(null, inset + i + inset); + } else if (el.xray) { + const info = el.xray(); + console.log(inset, info); + Object.values(info).forEach(val => { + if (val.xout) { + val.xout(null, inset + inset); } - } else { - // this.push.apply(this,arr); (slower?) - this.push(...arr); + }); + } + }); +}; + +/** + * remove and return element from array, if present + */ +AP.remove = function(val) { + var idx = this.indexOf(val); + if (idx >= 0) return this.splice(idx, 1); + return null; +}; + +AP.removeAll = function(array) { + for (let i = 0, il = array.length; i < il; i++) { + this.remove(array[i]); + } +}; + +/** + * return last array element if array length > 0 + */ +AP.last = function() { + if (this.length === 0) return null; + return this[this.length - 1]; +}; + +AP.contains = function(val) { + return this.indexOf(val) >= 0; +}; + +AP.toFloat32 = function() { + var i = 0, + f32 = new Float32Array(this.length); + while (i < this.length) { + f32[i] = this[i++]; + } + return f32; +}; + +AP.forEachPair = function(fn, incr) { + var scope = this, + idx = 0, + inc = incr || 2, + len = scope.length; + while (idx < len) { + fn(scope[idx], scope[(idx + 1) % len], idx); + idx += inc; + } +}; + +AP.show = function(fn, f) { + f = f || function(v) { + console.log(v) + }; + this.forEach(function(av) { + f(av[fn]()); + }) +}; + +AP.uniq = function() { + return this.slice().sort().filter((x, i, a) => !i || x != a[i - 1]); +}; + +AP.hasNaN = function() { + return this.filter(v => isNaN(v)).length > 0; +}; + +// turn array into arrays of specified size +AP.group = function(size) { + let na = new Array(Math.ceil(this.length / size)); + let nai = 0; + for (let i = 0; i < this.length; i += size) { + let grp = new Array(size); + for (let j = 0; j < size; j++) { + grp[j] = this[i + j]; + } + na[nai++] = grp; + } + return na; +}; + +// wrap a function and unroll the first parameter into +// successive function calls if it is an array +Array.handle = function(fn) { + return function() { + let args = [...arguments]; + let val = args.shift(); + if (Array.isArray(val)) { + let rv = []; + for (let v of val.slice()) { + rv.push(fn(v, ...args)); } + return rv; + } else { + return fn(val, ...args); } - return this; - }; + } +}; - AP.addOnce = function(val) { - if (!this.contains(val)) { - this.push(val); - } - return this; - }; +for (let i in AP) { + Object.defineProperty(Array.prototype, i, { + value: AP[i], + enumerable: false + }); +} - /** - * shallow cloning with clone(arg) call on each new element - * - * @param {Array} [arg] - * @returns {Array} - */ - AP.clone = function(arg) { - var na = this.slice(), - ln = na.length, - i = 0; - while (i < ln) na[i] = na[i++].clone(arg); - return na; - }; +String.prototype.reverse = function() { + return this.split('').reverse().join(''); +}; - AP.xray = function(arg) { - const na = [], ln = this.length; - for (let i=0; i { - if (el.xout) { - el.xout(null, inset + i + inset); - } else if (el.xray) { - const info = el.xray(); - console.log(inset, info); - Object.values(info).forEach(val => { - if (val.xout) { - val.xout(null, inset + inset); - } - }); - } - }); - }; - - /** - * remove and return element from array, if present - * - * @param val - * @returns {*} - */ - AP.remove = function(val) { - var idx = this.indexOf(val); - if (idx >= 0) return this.splice(idx,1); - return null; - }; - - AP.removeAll = function(array) { - for (let i=0, il=array.length; i 0 - * - * @returns {*} - */ - AP.last = function() { - if (this.length === 0) return null; - return this[this.length-1]; - }; - - AP.contains = function(val) { - return this.indexOf(val) >= 0; - }; - - AP.toFloat32 = function() { - var i = 0, f32 = new Float32Array(this.length); - while (i < this.length) { - f32[i] = this[i++]; - } - return f32; - }; - - AP.forEachPair = function(fn, incr) { - var scope = this, - idx = 0, - inc = incr || 2, - len = scope.length; - while (idx < len) { - fn(scope[idx], scope[(idx+1)%len], idx); - idx += inc; - } - }; - - AP.show = function(fn,f) { - f = f || function(v) { console.log(v) }; - this.forEach(function(av) { - f(av[fn]()); - }) - }; - - AP.uniq = function() { - return this.slice().sort().filter((x, i, a) => !i || x != a[i-1]); - }; - - AP.hasNaN = function() { - return this.filter(v => isNaN(v)).length > 0; - }; - - // turn array into arrays of specified size - AP.group = function(size) { - let na = new Array(Math.ceil(this.length / size)); - let nai = 0; - for (let i=0; i new Promise(function(resolve, reject) { + let { lower, upper, collect } = opt; + if (!collect) { + let rval = opt.map ? {} : []; + collect = (key, value) => { + if (key !== null) { + if (opt.map) { + rval[key] = value; + } else { + rval.push({key, value}); + } + } else { + resolve(rval); + } + }; + } else { + resolve(); + } + db.iterate(collect, lower, upper, true, store); + }), + + get: (key) => new Promise(function(resolve, reject) { + db.get(key, resolve, store); + }), + + put: (key, value) => new Promise(function(resolve, reject) { + db.put(key, value, resolve, store); + }), + + remove: (key) => new Promise(function(resolve, reject) { + db.remove(key, resolve, store); + }), + + // wait for all outstanding transactions to complete + wait: () => new Promise(function(resolve, reject) { + db.onIdle(promise); + }), + + use: (store) => { + db.use(store); + return pc; + }, }; + return pc; +} - // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API +SP.keys = function(callback, lower, upper, store = this.current) { + if (!this.db) { + this.init(); + return this.queue.push(["keys", callback, lower, upper, store]); + } + let out = []; + this.iterate(function(k,v) { + if (k === null) { + callback(out); + } else { + out.push(k); + } + }, lower, upper, true, store); +}; - let IDB, IRR, local = null; +SP.iterate = function(callback, lower, upper, nullterm, store = this.current) { + if (!this.db) { + this.init(); + return this.queue.push(["iterate", callback, lower, upper, nullterm, store]); + } + let range = lower && upper ? IRR.bound(lower,upper) : + lower ? IRR.lowerBound(lower) : + upper ? IRR.upperBound(upper) : undefined; + // iterate over all db values for debugging + this.db + .transaction(store) + .objectStore(store) + .openCursor(range).onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + callback(cursor.key, cursor.value); + cursor.continue(); + } else if (nullterm) { + callback(null); + } + }; +}; + +SP.deleteStore = function() { + if (this.initCalled) throw "cannot delete ObjectStore after init() called"; + this.version = Number.MAX_SAFE_INTEGER || Number.MAX_VALUE; + return this.init(true); +}; + +SP.use = function(store) { + this.current = store; + return this; +} + +SP.init = function(options = {}) { + if (this.initCalled) return; + + let storage = this, + name = this.name, + request = null; + + function fallback() { + console.log("in private browsing mode or browser lacks support for IndexedDB. unable to setup storage for '" + name + "'."); + local = {}; + storage.runQueue(); + } try { - IDB = self.indexedDB || self.mozIndexedDB || self.webkitIndexedDB || self.msIndexedDB; - IRR = self.IDBKeyRange; - } catch (e) { - console.log("in private or restricted browsing mode. database storage blocked. application may not function."); - } + request = IDB.open(name, this.version); - /** - * @param {String} dbname - * @param {number} [version] - * @constructor - */ - function IDBStore(dbname, options = {}) { - this.db = null; - this.name = dbname; - this.stores = options.stores || [ dbname ]; - this.version = options.version || 1; - this.queue = []; - this.idle = []; - this.initCalled = false; - this.created = {}; - } - - IDBStore.delete = function(dbname) { - IDB.deleteDatabase(dbname); - }; - - let SP = IDBStore.prototype; - - /** ****************************************************************** - * indexedDB implementation - ******************************************************************* */ - - SP.onIdle = function(fn) { - if (this.queue.length) { - this.idle.push(fn); - } else { - fn(); - } - return this; - }; - - SP.promise = function(store) { - let db = this; - let pc = { - iterate: (opt = {}) => new Promise(function(resolve, reject) { - let { lower, upper, collect } = opt; - if (!collect) { - let rval = opt.map ? {} : []; - collect = (key, value) => { - if (key !== null) { - if (opt.map) { - rval[key] = value; - } else { - rval.push({key, value}); - } - } else { - resolve(rval); - } - }; - } else { - resolve(); + request.onupgradeneeded = function(event) { + let db = storage.db = request.result; + let current = [...db.objectStoreNames]; + let stores = storage.stores; + // add missing stores + for (let store of stores) { + if (current.indexOf(store) >= 0) { + continue; } - db.iterate(collect, lower, upper, true, store); - }), - - get: (key) => new Promise(function(resolve, reject) { - db.get(key, resolve, store); - }), - - put: (key, value) => new Promise(function(resolve, reject) { - db.put(key, value, resolve, store); - }), - - remove: (key) => new Promise(function(resolve, reject) { - db.remove(key, resolve, store); - }), - - // wait for all outstanding transactions to complete - wait: () => new Promise(function(resolve, reject) { - db.onIdle(promise); - }), - - use: (store) => { - db.use(store); - return pc; - }, - }; - return pc; - } - - SP.keys = function(callback, lower, upper, store = this.current) { - if (!this.db) { - this.init(); - return this.queue.push(["keys", callback, lower, upper, store]); - } - let out = []; - this.iterate(function(k,v) { - if (k === null) { - callback(out); - } else { - out.push(k); + storage.created[store] = db.createObjectStore(store); + storage.current = store; + current.push(store); + console.log({index_added: store}); } - }, lower, upper, true, store); - }; - - SP.iterate = function(callback, lower, upper, nullterm, store = this.current) { - if (!this.db) { - this.init(); - return this.queue.push(["iterate", callback, lower, upper, nullterm, store]); - } - let range = lower && upper ? IRR.bound(lower,upper) : - lower ? IRR.lowerBound(lower) : - upper ? IRR.upperBound(upper) : undefined; - // iterate over all db values for debugging - this.db - .transaction(store) - .objectStore(store) - .openCursor(range).onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - callback(cursor.key, cursor.value); - cursor.continue(); - } else if (nullterm) { - callback(null); + // remove obsolete stores + for (let curr of current) { + if (stores.indexOf(curr) < 0) { + storage.db.deleteObjectStore(curr); + console.log({index_dropped: curr}); } - }; - }; - - SP.deleteStore = function() { - if (this.initCalled) throw "cannot delete ObjectStore after init() called"; - this.version = Number.MAX_SAFE_INTEGER || Number.MAX_VALUE; - return this.init(true); - }; - - SP.use = function(store) { - this.current = store; - return this; - } - - SP.init = function(options = {}) { - if (this.initCalled) return; - - let storage = this, - name = this.name, - request = null; - - function fallback() { - console.log("in private browsing mode or browser lacks support for IndexedDB. unable to setup storage for '" + name + "'."); - local = {}; - storage.runQueue(); - } - - try { - request = IDB.open(name, this.version); - - request.onupgradeneeded = function(event) { - let db = storage.db = request.result; - let current = [...db.objectStoreNames]; - let stores = storage.stores; - // add missing stores - for (let store of stores) { - if (current.indexOf(store) >= 0) { - continue; - } - storage.created[store] = db.createObjectStore(store); - storage.current = store; - current.push(store); - console.log({index_added: store}); - } - // remove obsolete stores - for (let curr of current) { - if (stores.indexOf(curr) < 0) { - storage.db.deleteObjectStore(curr); - console.log({index_dropped: curr}); - } - } - event.target.transaction.oncomplete = function(event) { - storage.runQueue(); - }; - }; - - request.onsuccess = function(event) { - storage.current = storage.stores[0]; - storage.db = request.result; + } + event.target.transaction.oncomplete = function(event) { storage.runQueue(); }; + }; - request.onerror = function(event) { - console.log({error: event}); - fallback(); - }; - } catch (e) { + request.onsuccess = function(event) { + storage.current = storage.stores[0]; + storage.db = request.result; + storage.runQueue(); + }; + + request.onerror = function(event) { + console.log({error: event}); fallback(); - return; - } + }; + } catch (e) { + fallback(); + return; + } - this.initCalled = true; - return this; - }; + this.initCalled = true; + return this; +}; - SP.runQueue = function() { - if (this.queue.length > 0) { - let i = 0, q = this.queue, e; - while (i < q.length) { - e = q[i++]; - switch (e.shift()) { - case 'iterate': this.iterate(...e); break; - case 'keys': this.keys(...e); break; - case 'put': this.put(...e); break; - case 'get': this.get(...e); break; - case 'remove': this.remove(...e); break; - case 'clear': this.clear(...e); - } +SP.runQueue = function() { + if (this.queue.length > 0) { + let i = 0, q = this.queue, e; + while (i < q.length) { + e = q[i++]; + switch (e.shift()) { + case 'iterate': this.iterate(...e); break; + case 'keys': this.keys(...e); break; + case 'put': this.put(...e); break; + case 'get': this.get(...e); break; + case 'remove': this.remove(...e); break; + case 'clear': this.clear(...e); } - this.queue = []; } - while (this.idle.length) { - this.idle.shift()(); - } - }; + this.queue = []; + } + while (this.idle.length) { + this.idle.shift()(); + } +}; - SP.put = function(key, value, callback, store = this.current) { - if (local) { - local[key] = value; - if (callback) callback(true); - return; - } - if (!this.db) { - this.init(); - return this.queue.push(['put', key, value, callback, store]); - } - try { - let req = this.db - .transaction(store, "readwrite") - .objectStore(store).put(value, key); - if (callback) { - req.onsuccess = function(event) { callback(true) }; - req.onerror = function(event) { callback(false) }; - } - } catch (e) { - console.log(e); - if (callback) callback(false); - } - }; - - SP.get = function(key, callback, store = this.current) { - if (local) { - if (callback) callback(local[key]); - return; - } - if (!this.db) { - this.init(); - return this.queue.push(['get', key, callback, store]); - } - try { - let req = this.db - .transaction(store) - .objectStore(store).get(key); - if (callback) { - req.onsuccess = function(event) { callback(req.result) }; - req.onerror = function(event) { callback(null) }; - } - } catch (e) { - console.log(e); - if (callback) callback(null); - } - }; - - SP.remove = function(key, callback, store = this.current) { - if (local) { - delete local[key]; - if (callback) callback(true); - return; - } - if (!this.db) { - this.init(); - return this.queue.push(['remove', key, callback, store]); - } - try { - let req = this.db - .transaction(store, "readwrite") - .objectStore(store).delete(key); - if (callback) { - req.onsuccess = function(event) { callback(true) }; - req.onerror = function(event) { callback(false) }; - } - } catch (e) { - console.log(e); - if (callback) callback(false); - } - }; - - SP.clear = function(key, store = this.current) { - if (!this.db) { - this.init(); - return this.queue.push(['clear', store]); - } - this.db +SP.put = function(key, value, callback, store = this.current) { + if (local) { + local[key] = value; + if (callback) callback(true); + return; + } + if (!this.db) { + this.init(); + return this.queue.push(['put', key, value, callback, store]); + } + try { + let req = this.db .transaction(store, "readwrite") - .objectStore(store).clear(); - }; + .objectStore(store).put(value, key); + if (callback) { + req.onsuccess = function(event) { callback(true) }; + req.onerror = function(event) { callback(false) }; + } + } catch (e) { + console.log(e); + if (callback) callback(false); + } +}; + +SP.get = function(key, callback, store = this.current) { + if (local) { + if (callback) callback(local[key]); + return; + } + if (!this.db) { + this.init(); + return this.queue.push(['get', key, callback, store]); + } + try { + let req = this.db + .transaction(store) + .objectStore(store).get(key); + if (callback) { + req.onsuccess = function(event) { callback(req.result) }; + req.onerror = function(event) { callback(null) }; + } + } catch (e) { + console.log(e); + if (callback) callback(null); + } +}; + +SP.remove = function(key, callback, store = this.current) { + if (local) { + delete local[key]; + if (callback) callback(true); + return; + } + if (!this.db) { + this.init(); + return this.queue.push(['remove', key, callback, store]); + } + try { + let req = this.db + .transaction(store, "readwrite") + .objectStore(store).delete(key); + if (callback) { + req.onsuccess = function(event) { callback(true) }; + req.onerror = function(event) { callback(false) }; + } + } catch (e) { + console.log(e); + if (callback) callback(false); + } +}; + +SP.clear = function(key, store = this.current) { + if (!this.db) { + this.init(); + return this.queue.push(['clear', store]); + } + this.db + .transaction(store, "readwrite") + .objectStore(store).clear(); +}; })(); diff --git a/src/data/local.js b/src/data/local.js index 7175a4de..c072da99 100644 --- a/src/data/local.js +++ b/src/data/local.js @@ -4,46 +4,46 @@ (function() { - gapp.register('data.local'); +gapp.register('data.local'); - let data = self.data = self.data || {}; - if (data.Local) return; +let data = self.data = self.data || {}; +if (data.Local) return; - function Local() { - this.__data__ = {}; - this.__mem__ = true; - } +function Local() { + this.__data__ = {}; + this.__mem__ = true; +} - var LS = Local.prototype; +var LS = Local.prototype; - LS.getItem = function(key) { - return this[key]; - }; +LS.getItem = function(key) { + return this[key]; +}; - LS.setItem = function(key, val) { - this.__data__[key] = val; - this[key] = val; - }; +LS.setItem = function(key, val) { + this.__data__[key] = val; + this[key] = val; +}; - LS.removeItem = function(key) { - delete this.__data__[key]; - }; +LS.removeItem = function(key) { + delete this.__data__[key]; +}; - LS.clear = function() { - this.__data__ = {}; - }; +LS.clear = function() { + this.__data__ = {}; +}; - try { - let local = data.Local = self.localStorage; - let testkey = '__test'; - local.setItem(testkey, 1); - local.getItem(testkey); - local.removeItem(testkey); - } catch (e) { - data.Local = new Local(); - let msg = "in private or restricted browsing mode. local storage blocked. application may not function properly."; - console.log(msg); - alert(msg); - } +try { + let local = data.Local = self.localStorage; + let testkey = '__test'; + local.setItem(testkey, 1); + local.getItem(testkey); + local.removeItem(testkey); +} catch (e) { + data.Local = new Local(); + let msg = "in private or restricted browsing mode. local storage blocked. application may not function properly."; + console.log(msg); + alert(msg); +} })();