- Upgraded "[_pathfinder_esi_](https://github.com/exodus4d/pathfinder_esi)" Web API client`v1.3.2` → `v2.0.0` - Fixed a js bug where current active(selected) system becomes deselected after system was dragged on map - Fixed a js bug where new auto mapped systems (e.g. after jump) were positioned outside current map scroll viewport - Fixed a js bug where map sync failed after map tabs switch - Fixed blurry map when map zoom was changed - Fixed multiple minor JS bugs where map render/update failed
394 lines
12 KiB
JavaScript
394 lines
12 KiB
JavaScript
define([
|
||
'localForage',
|
||
'app/promises/promise.queue',
|
||
'app/promises/promise.deferred',
|
||
], (LocalForage, PromiseQueue, DeferredPromise) => {
|
||
'use strict';
|
||
|
||
/**
|
||
* Instances of LocalStore handle its own LocalForage instance
|
||
*/
|
||
class LocalStore {
|
||
constructor(config, LocalForageConfig){
|
||
this._config = Object.assign({}, this.constructor.defaultConfig, config);
|
||
|
||
let initPromise = new DeferredPromise();
|
||
this._processQueue = new PromiseQueue();
|
||
this._processQueue.enqueue(() => initPromise);
|
||
|
||
this._localforage = LocalForage.createInstance(Object.assign({}, LocalStore.LocalForageConfig, LocalForageConfig));
|
||
this._localforage.ready().then(() => initPromise.resolve());
|
||
|
||
this._manager = null; // reference to LocalStoreManager() that manages this LocalStore instance
|
||
|
||
this.debug = (msg,...data) => {
|
||
if(this._config.debug){
|
||
data = (data || []);
|
||
data.unshift(this.constructor.name, this._config.name);
|
||
console.debug('debug: %s %o | ' + msg, ...data);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* set scope for this instance
|
||
* -> all read/write actions are scoped
|
||
* this is a prefix for all keys!
|
||
* @param scope
|
||
*/
|
||
set scope(scope){
|
||
if(LocalStore.isString(scope)){
|
||
this._config.scope = scope;
|
||
}else{
|
||
throw new TypeError('Scope must be instance of "String", Type of "' + typeof scope + '" given');
|
||
}
|
||
}
|
||
|
||
get scope(){
|
||
return this._config.scope;
|
||
}
|
||
|
||
/**
|
||
* get item
|
||
* @param key
|
||
* @param successCallback
|
||
* @returns {Promise}
|
||
*/
|
||
getItem(key, successCallback = undefined){
|
||
key = this.fixKey(key);
|
||
let propArray = LocalStore.keyToArray(key);
|
||
let rootKey = propArray.shift();
|
||
|
||
let getItem = () => this._localforage.getItem(key, successCallback);
|
||
if(propArray.length){
|
||
getItem = () => {
|
||
return this._localforage.getItem(rootKey)
|
||
.then(data => {
|
||
if(LocalStore.isObject(data)){
|
||
// find nested property
|
||
return LocalStore.findObjProp(data, propArray);
|
||
}else{
|
||
// rootKey not found -> propArray path not exists
|
||
return Promise.resolve(null);
|
||
}
|
||
});
|
||
};
|
||
}
|
||
|
||
return this._processQueue.enqueue(() => getItem());
|
||
}
|
||
|
||
/**
|
||
* set/update existing value
|
||
* @param key e.g. nested object key' first.a.b.test'
|
||
* @param value
|
||
* @param successCallback
|
||
* @returns {Promise}
|
||
*/
|
||
setItem(key, value, successCallback = undefined){
|
||
key = this.fixKey(key);
|
||
let propArray = LocalStore.keyToArray(key);
|
||
let rootKey = propArray.shift();
|
||
|
||
let getItem = () => Promise.resolve(value);
|
||
if(propArray.length){
|
||
getItem = () => {
|
||
return this._localforage.getItem(rootKey)
|
||
.then(rootVal => {
|
||
rootVal = (rootVal === null) ? {} : rootVal;
|
||
// update data with new value (merge obj)
|
||
LocalStore.updateObjProp(rootVal, value, propArray);
|
||
return rootVal;
|
||
});
|
||
};
|
||
}
|
||
|
||
return this._processQueue.enqueue(() =>
|
||
getItem()
|
||
.then(rootVal => this._localforage.setItem(rootKey, rootVal, successCallback))
|
||
.then(() => Promise.resolve(value))
|
||
);
|
||
}
|
||
|
||
/**
|
||
* remove item by key
|
||
* -> allows deep obj delete if key points to a nested obj prop
|
||
* @param key
|
||
* @param successCallback
|
||
* @returns {Promise}
|
||
*/
|
||
removeItem(key, successCallback = undefined){
|
||
key = this.fixKey(key);
|
||
let propArray = LocalStore.keyToArray(key);
|
||
let rootKey = propArray.shift();
|
||
|
||
let removeItem = () => this._localforage.removeItem(rootKey, successCallback);
|
||
if(propArray.length){
|
||
removeItem = () => {
|
||
return this._localforage.getItem(rootKey)
|
||
.then(data => {
|
||
if(LocalStore.isObject(data)){
|
||
// update data -> delete nested prop
|
||
LocalStore.deleteObjProp(data, propArray);
|
||
return data;
|
||
}else{
|
||
// rootKey not found -> nothing to delete
|
||
return Promise.reject(new RangeError('No data found for key: ' + rootKey));
|
||
}
|
||
})
|
||
.then(value => this._localforage.setItem(rootKey, value, successCallback))
|
||
.catch(e => this.debug('removeItem() error',e));
|
||
};
|
||
}
|
||
|
||
return this._processQueue.enqueue(() => removeItem());
|
||
}
|
||
|
||
/**
|
||
* clear all items in store
|
||
* @param successCallback
|
||
* @returns {Promise}
|
||
*/
|
||
clear(successCallback = undefined){
|
||
return this._processQueue.enqueue(() => this._localforage.clear(successCallback));
|
||
}
|
||
|
||
/**
|
||
* get number of keys in store
|
||
* @param successCallback
|
||
* @returns {Promise}
|
||
*/
|
||
length(successCallback = undefined){
|
||
return this._processQueue.enqueue(() => this._localforage.length(successCallback));
|
||
}
|
||
|
||
/**
|
||
* Get the name of a key based on its index
|
||
* @param keyIndex
|
||
* @param successCallback
|
||
* @returns {Promise|void}
|
||
*/
|
||
key(keyIndex, successCallback = undefined){
|
||
return this._processQueue.enqueue(() => this._localforage.key(keyIndex, successCallback));
|
||
}
|
||
|
||
/**
|
||
* get list of all keys in store
|
||
* @param successCallback
|
||
* @returns {Promise|void}
|
||
*/
|
||
keys(successCallback = undefined){
|
||
return this._processQueue.enqueue(() => this._localforage.keys(successCallback));
|
||
}
|
||
|
||
/**
|
||
* drop current LocalForage instance
|
||
* -> removes this from LocalStoreManager
|
||
* @returns {Promise|void}
|
||
*/
|
||
dropInstance(){
|
||
return this._processQueue.enqueue(() =>
|
||
this._localforage.dropInstance().then(() => this._manager.deleteStore(this._config.name))
|
||
);
|
||
}
|
||
|
||
/**
|
||
* connect LocalStoreManager with instance
|
||
* @param {LocalStoreManager} manager
|
||
*/
|
||
connect(manager){
|
||
if(manager instanceof LocalStoreManager){
|
||
this._manager = manager;
|
||
}else{
|
||
throw new TypeError('Parameter must be instance of LocalStoreManager. Type of "' + typeof manager + '" given');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* check if key is Int or String with Int at pos 0
|
||
* -> prefix key
|
||
* @param key
|
||
* @returns {string}
|
||
*/
|
||
fixKey(key){
|
||
if(LocalStore.isString(this.scope) && this.scope.length){
|
||
key = [this.scope, key].join('.');
|
||
}
|
||
|
||
if(
|
||
Number.isInteger(key) ||
|
||
(LocalStore.isString(key) && parseInt(key.charAt(0), 10))
|
||
){
|
||
key = [this._config.name, key].join('_');
|
||
}
|
||
return key;
|
||
}
|
||
|
||
/**
|
||
* find data from obj prop
|
||
* -> deep object search
|
||
* @param obj
|
||
* @param propArray
|
||
* @returns {null|*}
|
||
*/
|
||
static findObjProp(obj, propArray){
|
||
let [head, ...rest] = propArray;
|
||
if(!rest.length){
|
||
return obj[head];
|
||
}else{
|
||
if(LocalStore.isObject(obj[head])){
|
||
return LocalStore.findObjProp(obj[head], rest);
|
||
}else{
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* update/extend obj with new value
|
||
* -> deep object manipulation
|
||
* @param obj
|
||
* @param value
|
||
* @param propArray
|
||
*/
|
||
static updateObjProp(obj, value, propArray){
|
||
let [head, ...rest] = propArray;
|
||
if(!rest.length){
|
||
obj[head] = value;
|
||
}else{
|
||
if(!LocalStore.isObject(obj[head])) obj[head] = {};
|
||
LocalStore.updateObjProp(obj[head], value, rest);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* delete object prop by propArray path
|
||
* -> deep object delete
|
||
* @param obj
|
||
* @param propArray
|
||
*/
|
||
static deleteObjProp(obj, propArray){
|
||
let [head, ...rest] = propArray;
|
||
if(!rest.length){
|
||
delete obj[head];
|
||
}else{
|
||
if(LocalStore.isObject(obj[head])){
|
||
LocalStore.deleteObjProp(obj[head], rest);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* converts string key to array
|
||
* @param propPath
|
||
* @returns {*|string[]}
|
||
*/
|
||
static keyToArray(propPath){
|
||
return propPath.split('.');
|
||
}
|
||
|
||
/**
|
||
* build DB name
|
||
* @param name
|
||
* @returns {string}
|
||
*/
|
||
static buildDbName(name){
|
||
return [LocalStore.dbNamePrefix, name].join(' ');
|
||
}
|
||
|
||
/**
|
||
* check var for Object
|
||
* @param obj
|
||
* @returns {boolean}
|
||
*/
|
||
static isObject(obj){
|
||
return (!!obj) && (obj.constructor === Object);
|
||
}
|
||
|
||
/**
|
||
* check var for Array
|
||
* @param arr
|
||
* @returns {boolean}
|
||
*/
|
||
static isArray(arr){
|
||
return (!!arr) && (arr.constructor === Array);
|
||
}
|
||
|
||
/**
|
||
* check var for String
|
||
* @param str
|
||
* @returns {boolean}
|
||
*/
|
||
static isString(str){
|
||
return typeof str === 'string';
|
||
}
|
||
}
|
||
|
||
LocalStore.defaultConfig = {
|
||
name: 'default', // custom unique name for identification
|
||
debug: false
|
||
};
|
||
|
||
LocalStore.dbNamePrefix = 'PathfinderDB';
|
||
LocalStore.LocalForageConfig = {
|
||
driver: [LocalForage.INDEXEDDB, LocalForage.WEBSQL, LocalForage.LOCALSTORAGE],
|
||
name: LocalStore.dbNamePrefix
|
||
};
|
||
|
||
/**
|
||
* An instance of LocalStoreManager() handles multiple LocalStore()´s
|
||
* -> LocalStore()´s can be set()/delete() from LocalStore() instance
|
||
*/
|
||
class LocalStoreManager {
|
||
|
||
constructor(){
|
||
if(!this.constructor.instance){
|
||
this._store = new Map();
|
||
this.constructor.instance = this;
|
||
}
|
||
|
||
return this.constructor.instance;
|
||
}
|
||
|
||
/**
|
||
* get LocalStore instance by name
|
||
* @param name
|
||
* @returns {LocalStore}
|
||
*/
|
||
getStore(name){
|
||
return this.newStore(name);
|
||
}
|
||
|
||
/**
|
||
* get either existing LocalStore instance
|
||
* -> or create new instance
|
||
* @param name
|
||
* @returns {LocalStore|undefined}
|
||
*/
|
||
newStore(name){
|
||
if(!this._store.has(name)){
|
||
let store = new LocalStore({
|
||
name: name
|
||
}, {
|
||
name: LocalStore.buildDbName(name)
|
||
});
|
||
store.connect(this);
|
||
this._store.set(name, store);
|
||
}
|
||
return this._store.get(name);
|
||
}
|
||
|
||
/**
|
||
* removes LocalStore instance from Manager
|
||
* -> this will not drop LocalForage instance!
|
||
* check LocalStore.dropInstance() for graceful delete
|
||
* @param name
|
||
* @returns {boolean}
|
||
*/
|
||
deleteStore(name){
|
||
return this._store.delete(name);
|
||
}
|
||
}
|
||
|
||
return new LocalStoreManager();
|
||
}); |