phaser4-rex-plugins
Version:
1,438 lines (1,228 loc) • 370 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.rexcharactercacheplugin = factory());
})(this, (function () { 'use strict';
var EventEmitterMethods = {
setEventEmitter(eventEmitter, EventEmitterClass) {
if (EventEmitterClass === undefined) {
EventEmitterClass = Phaser.Events.EventEmitter; // Use built-in EventEmitter class by default
}
this._privateEE = (eventEmitter === true) || (eventEmitter === undefined);
this._eventEmitter = (this._privateEE) ? (new EventEmitterClass()) : eventEmitter;
return this;
},
destroyEventEmitter() {
if (this._eventEmitter && this._privateEE) {
this._eventEmitter.shutdown();
}
return this;
},
getEventEmitter() {
return this._eventEmitter;
},
on() {
if (this._eventEmitter) {
this._eventEmitter.on.apply(this._eventEmitter, arguments);
}
return this;
},
once() {
if (this._eventEmitter) {
this._eventEmitter.once.apply(this._eventEmitter, arguments);
}
return this;
},
off() {
if (this._eventEmitter) {
this._eventEmitter.off.apply(this._eventEmitter, arguments);
}
return this;
},
emit(event) {
if (this._eventEmitter && event) {
this._eventEmitter.emit.apply(this._eventEmitter, arguments);
}
return this;
},
addListener() {
if (this._eventEmitter) {
this._eventEmitter.addListener.apply(this._eventEmitter, arguments);
}
return this;
},
removeListener() {
if (this._eventEmitter) {
this._eventEmitter.removeListener.apply(this._eventEmitter, arguments);
}
return this;
},
removeAllListeners() {
if (this._eventEmitter) {
this._eventEmitter.removeAllListeners.apply(this._eventEmitter, arguments);
}
return this;
},
listenerCount() {
if (this._eventEmitter) {
return this._eventEmitter.listenerCount.apply(this._eventEmitter, arguments);
}
return 0;
},
listeners() {
if (this._eventEmitter) {
return this._eventEmitter.listeners.apply(this._eventEmitter, arguments);
}
return [];
},
eventNames() {
if (this._eventEmitter) {
return this._eventEmitter.eventNames.apply(this._eventEmitter, arguments);
}
return [];
},
};
const GameClass = Phaser.Game;
var IsGame = function (object) {
return (object instanceof GameClass);
};
const SceneClass = Phaser.Scene;
var IsSceneObject = function (object) {
return (object instanceof SceneClass);
};
var GetGame = function (object) {
if ((object == null) || (typeof (object) !== 'object')) {
return null;
} else if (IsGame(object)) {
return object;
} else if (IsGame(object.game)) {
return object.game;
} else if (IsSceneObject(object)) { // object = scene object
return object.sys.game;
} else if (IsSceneObject(object.scene)) { // object = game object
return object.scene.sys.game;
}
};
var GetWhiteFrame = function (game) {
return GetGame(game).textures.getFrame('__WHITE');
};
var DynamicTextureClearRectangle = function (texture, x, y, width, height) {
if (WhiteFrameWidth === undefined) {
var whiteFrame = GetWhiteFrame(texture.manager.game);
WhiteFrameWidth = whiteFrame.cutWidth;
WhiteFrameHeight = whiteFrame.cutHeight;
}
texture.stamp('__WHITE', undefined, x, y, {
scaleX: width / WhiteFrameWidth,
scaleY: height / WhiteFrameHeight,
originX: 0,
originY: 0,
erase: true,
});
return texture;
};
var WhiteFrameWidth;
var WhiteFrameHeight;
var Draw = function (frameName, callback, scope) {
var index = this.getFrameIndex(frameName);
if (index === -1) {
index = this.getFrameIndex(undefined);
}
if (index === -1) {
console.warn('Does not have free space.');
return this;
}
var tl = this.getTopLeftPosition(index),
outerX = tl.x,
outerY = tl.y,
cellPadding = this.cellPadding,
innerX = outerX + cellPadding,
innerY = outerY + cellPadding;
ClearFrame.call(this, outerX, outerY, this.outerCellWidth, this.outerCellHeight);
var frameSize = {
width: this.cellWidth,
height: this.cellHeight
};
var drawCallback = (this.useDynamicTexture) ? DrawDynamicTexture : DrawCanvasTexture;
drawCallback.call(this, innerX, innerY, frameSize, callback, scope);
// frameSize might be changed
this.texture.add(frameName, 0, innerX, innerY, frameSize.width, frameSize.height);
this.addFrameName(index, frameName);
this.dirty = true;
return this;
};
var ClearFrame = function (x, y, width, height) {
if (this.useDynamicTexture) {
DynamicTextureClearRectangle(this.texture, x, y, width, height);
} else {
this.context.clearRect(x, y, width, height);
}
};
var DrawCanvasTexture = function (x, y, frameSize, callback, scope) {
var context = this.context;
context.save();
context.translate(x, y);
// Draw cell
if (scope) {
callback.call(scope, this.canvas, context, frameSize);
} else {
callback(this.canvas, context, frameSize);
}
// frameSize might be changed
context.restore();
};
var DrawDynamicTexture = function (x, y, frameSize, callback, scope) {
var texture = this.texture;
// Draw cell
texture.camera.setScroll(-x, -y);
if (scope) {
callback.call(scope, texture, frameSize);
} else {
callback(texture, frameSize);
}
texture.camera.setScroll(0, 0);
// frameSize might be changed
};
var GetDisplayWidth = function (gameObject) {
if (gameObject.displayWidth !== undefined) {
return gameObject.displayWidth;
} else {
return gameObject.width;
}
};
var GetDisplayHeight = function (gameObject) {
if (gameObject.displayHeight !== undefined) {
return gameObject.displayHeight;
} else {
return gameObject.height;
}
};
var Paste = function (frameName, gameObject) {
var drawCallback;
if (this.useDynamicTexture) {
var srcWidth = GetDisplayWidth(gameObject),
srcHeight = GetDisplayHeight(gameObject);
var scale;
if ((srcWidth <= this.cellWidth) && (srcHeight <= this.cellHeight)) {
scale = 1;
} else {
// Scale down and keep ratio
scale = Math.max((srcWidth / this.cellWidth), (srcHeight / this.cellHeight));
}
drawCallback = function (texture, frameSize) {
var originXSave = gameObject.originX,
originYSave = gameObject.originY;
var scaleXSave = gameObject.scaleX,
scaleYSave = gameObject.scaleY;
gameObject
.setOrigin(0, 0)
.setScale(scale, scale);
texture.draw(gameObject);
gameObject
.setOrigin(originXSave, originYSave)
.setScale(scaleXSave, scaleYSave);
frameSize.width = srcWidth / scale;
frameSize.height = srcHeight / scale;
};
} else {
var srcCanvas = gameObject.canvas;
if (!srcCanvas) {
console.warn(`Can't get canvas of game object.`);
return this;
}
var srcWidth = srcCanvas.width,
srcHeight = srcCanvas.height;
var dWidth, dHeight;
if ((srcWidth <= this.cellWidth) && (srcHeight <= this.cellHeight)) {
dWidth = srcWidth;
dHeight = srcHeight;
} else {
// Scale down and keep ratio
var scale = Math.max((srcWidth / this.cellWidth), (srcHeight / this.cellHeight));
dWidth = srcWidth / scale;
dHeight = srcHeight / scale;
}
drawCallback = function (canvas, context, frameSize) {
context.drawImage(srcCanvas, 0, 0, dWidth, dHeight);
frameSize.width = dWidth;
frameSize.height = dHeight;
};
}
this.draw(frameName, drawCallback);
return this;
};
var AddEmptyFrame = function (frameName, width, height) {
if (width === undefined) {
width = this.cellWidth;
}
if (height === undefined) {
height = this.cellHeight;
}
var drawCallback;
if (this.useDynamicTexture) {
drawCallback = function (texture, frameSize) {
frameSize.width = width;
frameSize.height = height;
};
} else {
drawCallback = function (canvas, context, frameSize) {
frameSize.width = width;
frameSize.height = height;
};
}
this.draw(frameName, drawCallback);
return this;
};
var RemoveMethods = {
// Remove a frame
remove(frameName) {
var index = this.getFrameIndex(frameName);
if (index === -1) {
return this;
}
this.addFrameName(index, undefined);
this.texture.remove(frameName);
// Don't clear canvas
return this;
},
// Remove all frames
clear() {
for (var i, cnt = this.frameNames.length; i < cnt; i++) {
var frameName = this.frameNames[i];
if (frameName !== undefined) {
this.addFrameName(index, undefined);
this.texture.remove(frameName);
}
}
return this;
}
};
var AddToBitmapFont = function () {
var textureKey = this.texture.key;
// Don't add a new font data, reuse current font data
var cacheData = this.bitmapFontCache.get(textureKey);
if (!cacheData) {
cacheData = {
data: {
retroFont: true,
font: textureKey,
size: this.cellWidth,
lineHeight: this.cellHeight,
chars: {}
},
texture: textureKey,
frame: null,
};
this.bitmapFontCache.add(textureKey, cacheData);
}
var charData = cacheData.data.chars;
var letters = this.frameNames;
for (var i = 0, cnt = letters.length; i < cnt; i++) {
var char = letters[i];
if (char === undefined) {
continue;
}
var frame = this.texture.get(char);
var x = frame.cutX,
y = frame.cutY,
width = frame.cutWidth,
height = frame.cutHeight;
charData[char.charCodeAt(0)] = {
x: x, y: y,
width: width, height: height,
centerX: x + (width / 2),
centerY: y + (height / 2),
xOffset: 0,
yOffset: 0,
xAdvance: width,
data: {},
kerning: {},
u0: frame.u0,
v0: frame.v0,
u1: frame.u1,
v1: frame.v1
};
}
return this;
};
var methods = {
draw: Draw,
paste: Paste,
addEmptyFrame: AddEmptyFrame,
addToBitmapFont: AddToBitmapFont,
};
Object.assign(
methods,
RemoveMethods
);
var CreateTexture = function (game, key, width, height, useDynamicTexture) {
game = GetGame(game);
if (useDynamicTexture === undefined) {
useDynamicTexture = false;
}
var textureManager = game.textures;
if (textureManager.exists(key)) {
textureManager.remove(key);
}
var methodName = (useDynamicTexture) ? 'addDynamicTexture' : 'createCanvas';
return textureManager[methodName](key, width, height);
};
const IsPlainObject$1 = Phaser.Utils.Objects.IsPlainObject;
const GetValue$3 = Phaser.Utils.Objects.GetValue;
class FrameManager {
constructor(scene, key, width, height, cellWidth, cellHeight, fillColor, useDynamicTexture) {
var columns, rows, cellPadding;
if (IsPlainObject$1(key)) {
var config = key;
key = GetValue$3(config, 'key');
width = GetValue$3(config, 'width');
height = GetValue$3(config, 'height');
cellWidth = GetValue$3(config, 'cellWidth');
cellHeight = GetValue$3(config, 'cellHeight');
cellPadding = GetValue$3(config, 'cellPadding', 0);
columns = GetValue$3(config, 'columns');
rows = GetValue$3(config, 'rows');
fillColor = GetValue$3(config, 'fillColor');
useDynamicTexture = GetValue$3(config, 'useDynamicTexture');
} else {
if (typeof (fillColor) === 'boolean') {
useDynamicTexture = fillColor;
fillColor = undefined;
}
}
if (cellWidth === undefined) {
cellWidth = 64;
}
if (cellHeight === undefined) {
cellHeight = 64;
}
if (cellPadding === undefined) {
cellPadding = 0;
}
this.scene = scene;
this.cellWidth = cellWidth;
this.cellHeight = cellHeight;
this.cellPadding = cellPadding;
this.outerCellWidth = cellWidth + (cellPadding * 2);
this.outerCellHeight = cellHeight + (cellPadding * 2);
if (columns) {
width = this.outerCellWidth * columns;
} else {
if (width === undefined) {
width = 4096;
}
columns = Math.floor(width / this.outerCellWidth);
}
if (rows) {
height = this.outerCellHeight * rows;
} else {
if (height === undefined) {
height = 4096;
}
rows = Math.floor(height / this.outerCellHeight);
}
if (useDynamicTexture === undefined) {
useDynamicTexture = false;
}
var game = GetGame(scene);
this.useDynamicTexture = useDynamicTexture;
this.texture = CreateTexture(game, key, width, height, useDynamicTexture);
this.canvas = (useDynamicTexture) ? undefined : this.texture.getCanvas();
this.context = (useDynamicTexture) ? undefined : this.texture.getContext();
this.bitmapFontCache = game.cache.bitmapFont;
if (fillColor !== undefined) {
if (useDynamicTexture) {
this.texture.fill(fillColor);
} else {
var context = this.context;
context.fillStyle = fillColor;
context.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
}
this.key = key;
this.width = width;
this.height = height;
this.columns = columns;
this.rows = rows;
this.totalCount = this.columns * this.rows;
this.fillColor = fillColor;
this.frameNames = Array(this.totalCount);
for (var i = 0, cnt = this.frameNames.length; i < cnt; i++) {
this.frameNames[i] = undefined;
}
this.dirty = false;
}
destroy() {
this.scene = undefined;
this.texture = undefined;
this.canvas = undefined;
this.context = undefined;
this.frameNames = undefined;
this.bitmapFontCache = undefined;
}
getFrameIndex(frameName) {
return this.frameNames.indexOf(frameName);
}
contains(frameName) {
return this.getFrameIndex(frameName) !== -1;
}
addFrameName(index, frameName) {
this.frameNames[index] = frameName;
return this;
}
get isFull() {
return this.getFrameIndex(undefined) === -1;
}
getTopLeftPosition(frameIndex, out) {
if (out === undefined) {
out = {};
}
var columnIndex = frameIndex % this.columns;
var rowIndex = Math.floor(frameIndex / this.columns);
out.x = columnIndex * (this.cellWidth + (this.cellPadding * 2));
out.y = rowIndex * (this.cellHeight + (this.cellPadding * 2));
return out;
}
updateTexture() {
if (this.useDynamicTexture) ; else {
this.texture.refresh();
}
this.dirty = false;
return this;
}
}
Object.assign(
FrameManager.prototype,
methods
);
const GetValue$2 = Phaser.Utils.Objects.GetValue;
var CreateFrameManager = function (scene, config) {
var key = GetValue$2(config, 'key');
var cellWidth = GetValue$2(config, 'cellWidth', 32);
var cellHeight = GetValue$2(config, 'cellHeight', 32);
var maxCharacterCount = GetValue$2(config, 'maxCharacterCount', 4096);
var colCount = Math.ceil(Math.sqrt(maxCharacterCount));
var rowCount = colCount;
var width = cellWidth * colCount;
var height = cellHeight * rowCount;
var frameManager = new FrameManager(scene, key, width, height, cellWidth, cellHeight);
return frameManager;
};
var global$1 = (typeof global !== "undefined" ? global :
typeof self !== "undefined" ? self :
typeof window !== "undefined" ? window : {});
// shim for using process in browser
// based off https://github.com/defunctzombie/node-process/blob/master/browser.js
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
throw new Error('clearTimeout has not been defined');
}
var cachedSetTimeout = defaultSetTimout;
var cachedClearTimeout = defaultClearTimeout;
if (typeof global$1.setTimeout === 'function') {
cachedSetTimeout = setTimeout;
}
if (typeof global$1.clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
}
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
}
// if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch(e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch(e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
}
// if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
function nextTick(fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
}
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
var title = 'browser';
var platform = 'browser';
var browser = true;
var env = {};
var argv = [];
var version = ''; // empty string to avoid regexp issues
var versions = {};
var release = {};
var config = {};
function noop() {}
var on = noop;
var addListener = noop;
var once = noop;
var off = noop;
var removeListener = noop;
var removeAllListeners = noop;
var emit = noop;
function binding(name) {
throw new Error('process.binding is not supported');
}
function cwd () { return '/' }
function chdir (dir) {
throw new Error('process.chdir is not supported');
}function umask() { return 0; }
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
var performance = global$1.performance || {};
var performanceNow =
performance.now ||
performance.mozNow ||
performance.msNow ||
performance.oNow ||
performance.webkitNow ||
function(){ return (new Date()).getTime() };
// generate timestamp or delta
// see http://nodejs.org/api/process.html#process_process_hrtime
function hrtime(previousTimestamp){
var clocktime = performanceNow.call(performance)*1e-3;
var seconds = Math.floor(clocktime);
var nanoseconds = Math.floor((clocktime%1)*1e9);
if (previousTimestamp) {
seconds = seconds - previousTimestamp[0];
nanoseconds = nanoseconds - previousTimestamp[1];
if (nanoseconds<0) {
seconds--;
nanoseconds += 1e9;
}
}
return [seconds,nanoseconds]
}
var startTime = new Date();
function uptime() {
var currentTime = new Date();
var dif = currentTime - startTime;
return dif / 1000;
}
var process = {
nextTick: nextTick,
title: title,
browser: browser,
env: env,
argv: argv,
version: version,
versions: versions,
on: on,
addListener: addListener,
once: once,
off: off,
removeListener: removeListener,
removeAllListeners: removeAllListeners,
emit: emit,
binding: binding,
cwd: cwd,
chdir: chdir,
umask: umask,
hrtime: hrtime,
platform: platform,
release: release,
config: config,
uptime: uptime
};
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function getAugmentedNamespace(n) {
if (n.__esModule) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a () {
if (this instanceof a) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
function commonjsRequire(path) {
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
}
var lokijs = {exports: {}};
var lokiIndexedAdapter = {exports: {}};
/*
Loki IndexedDb Adapter (need to include this script to use it)
Console Usage can be used for management/diagnostic, here are a few examples :
adapter.getDatabaseList(); // with no callback passed, this method will log results to console
adapter.saveDatabase('UserDatabase', JSON.stringify(myDb));
adapter.loadDatabase('UserDatabase'); // will log the serialized db to console
adapter.deleteDatabase('UserDatabase');
*/
var hasRequiredLokiIndexedAdapter;
function requireLokiIndexedAdapter () {
if (hasRequiredLokiIndexedAdapter) return lokiIndexedAdapter.exports;
hasRequiredLokiIndexedAdapter = 1;
(function (module, exports) {
(function (root, factory) {
{
// Node, CommonJS-like
module.exports = factory();
}
}(commonjsGlobal, function () {
return (function() {
/**
* Loki persistence adapter class for indexedDb.
* This class fulfills abstract adapter interface which can be applied to other storage methods.
* Utilizes the included LokiCatalog app/key/value database for actual database persistence.
* Indexeddb is highly async, but this adapter has been made 'console-friendly' as well.
* Anywhere a callback is omitted, it should return results (if applicable) to console.
* IndexedDb storage is provided per-domain, so we implement app/key/value database to
* allow separate contexts for separate apps within a domain.
*
* @example
* var idbAdapter = new LokiIndexedAdapter('finance');
*
* @constructor LokiIndexedAdapter
*
* @param {string} appname - (Optional) Application name context can be used to distinguish subdomains, 'loki' by default
* @param {object=} options Configuration options for the adapter
* @param {boolean} options.closeAfterSave Whether the indexedDB database should be closed after saving.
*/
function LokiIndexedAdapter(appname, options)
{
this.app = 'loki';
this.options = options || {};
if (typeof (appname) !== 'undefined')
{
this.app = appname;
}
// keep reference to catalog class for base AKV operations
this.catalog = null;
if (!this.checkAvailability()) {
throw new Error('indexedDB does not seem to be supported for your environment');
}
}
/**
* Used for closing the indexeddb database.
*/
LokiIndexedAdapter.prototype.closeDatabase = function ()
{
if (this.catalog && this.catalog.db) {
this.catalog.db.close();
this.catalog.db = null;
}
};
/**
* Used to check if adapter is available
*
* @returns {boolean} true if indexeddb is available, false if not.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.checkAvailability = function()
{
if (typeof indexedDB !== 'undefined' && indexedDB) return true;
return false;
};
/**
* Retrieves a serialized db string from the catalog.
*
* @example
* // LOAD
* var idbAdapter = new LokiIndexedAdapter('finance');
* var db = new loki('test', { adapter: idbAdapter });
* db.loadDatabase(function(result) {
* console.log('done');
* });
*
* @param {string} dbname - the name of the database to retrieve.
* @param {function} callback - callback should accept string param containing serialized db string.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.loadDatabase = function(dbname, callback)
{
var appName = this.app;
var adapter = this;
// lazy open/create db reference so dont -need- callback in constructor
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.loadDatabase(dbname, callback);
});
return;
}
// lookup up db string in AKV db
this.catalog.getAppKey(appName, dbname, function(result) {
if (typeof (callback) === 'function') {
if (result.id === 0) {
callback(null);
return;
}
callback(result.val);
}
else {
// support console use of api
console.log(result.val);
}
});
};
// alias
LokiIndexedAdapter.prototype.loadKey = LokiIndexedAdapter.prototype.loadDatabase;
/**
* Saves a serialized db to the catalog.
*
* @example
* // SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}
* var idbAdapter = new LokiIndexedAdapter('finance');
* var db = new loki('test', { adapter: idbAdapter });
* var coll = db.addCollection('testColl');
* coll.insert({test: 'val'});
* db.saveDatabase(); // could pass callback if needed for async complete
*
* @param {string} dbname - the name to give the serialized database within the catalog.
* @param {string} dbstring - the serialized db string to save.
* @param {function} callback - (Optional) callback passed obj.success with true or false
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.saveDatabase = function(dbname, dbstring, callback)
{
var appName = this.app;
var adapter = this;
function saveCallback(result) {
if (result && result.success === true) {
callback(null);
}
else {
callback(new Error("Error saving database"));
}
if (adapter.options.closeAfterSave) {
adapter.closeDatabase();
}
}
// lazy open/create db reference so dont -need- callback in constructor
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.saveDatabase(dbname, dbstring, saveCallback);
});
return;
}
// set (add/update) entry to AKV database
this.catalog.setAppKey(appName, dbname, dbstring, saveCallback);
};
// alias
LokiIndexedAdapter.prototype.saveKey = LokiIndexedAdapter.prototype.saveDatabase;
/**
* Deletes a serialized db from the catalog.
*
* @example
* // DELETE DATABASE
* // delete 'finance'/'test' value from catalog
* idbAdapter.deleteDatabase('test', function {
* // database deleted
* });
*
* @param {string} dbname - the name of the database to delete from the catalog.
* @param {function=} callback - (Optional) executed on database delete
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.deleteDatabase = function(dbname, callback)
{
var appName = this.app;
var adapter = this;
// lazy open/create db reference and pass callback ahead
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.deleteDatabase(dbname, callback);
});
return;
}
// catalog was already initialized, so just lookup object and delete by id
this.catalog.getAppKey(appName, dbname, function(result) {
var id = result.id;
if (id !== 0) {
adapter.catalog.deleteAppKey(id, callback);
} else if (typeof (callback) === 'function') {
callback({ success: true });
}
});
};
// alias
LokiIndexedAdapter.prototype.deleteKey = LokiIndexedAdapter.prototype.deleteDatabase;
/**
* Removes all database partitions and pages with the base filename passed in.
* This utility method does not (yet) guarantee async deletions will be completed before returning
*
* @param {string} dbname - the base filename which container, partitions, or pages are derived
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.deleteDatabasePartitions = function(dbname) {
var self=this;
this.getDatabaseList(function(result) {
result.forEach(function(str) {
if (str.startsWith(dbname)) {
self.deleteDatabase(str);
}
});
});
};
/**
* Retrieves object array of catalog entries for current app.
*
* @example
* idbAdapter.getDatabaseList(function(result) {
* // result is array of string names for that appcontext ('finance')
* result.forEach(function(str) {
* console.log(str);
* });
* });
*
* @param {function} callback - should accept array of database names in the catalog for current app.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.getDatabaseList = function(callback)
{
var appName = this.app;
var adapter = this;
// lazy open/create db reference so dont -need- callback in constructor
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.getDatabaseList(callback);
});
return;
}
// catalog already initialized
// get all keys for current appName, and transpose results so just string array
this.catalog.getAppKeys(appName, function(results) {
var names = [];
for(var idx = 0; idx < results.length; idx++) {
names.push(results[idx].key);
}
if (typeof (callback) === 'function') {
callback(names);
}
else {
names.forEach(function(obj) {
console.log(obj);
});
}
});
};
// alias
LokiIndexedAdapter.prototype.getKeyList = LokiIndexedAdapter.prototype.getDatabaseList;
/**
* Allows retrieval of list of all keys in catalog along with size
*
* @param {function} callback - (Optional) callback to accept result array.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.getCatalogSummary = function(callback)
{
this.app;
var adapter = this;
// lazy open/create db reference
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.getCatalogSummary(callback);
});
return;
}
// catalog already initialized
// get all keys for current appName, and transpose results so just string array
this.catalog.getAllKeys(function(results) {
var entries = [];
var obj,
size,
oapp,
okey,
oval;
for(var idx = 0; idx < results.length; idx++) {
obj = results[idx];
oapp = obj.app || '';
okey = obj.key || '';
oval = obj.val || '';
// app and key are composited into an appkey column so we will mult by 2
size = oapp.length * 2 + okey.length * 2 + oval.length + 1;
entries.push({ "app": obj.app, "key": obj.key, "size": size });
}
if (typeof (callback) === 'function') {
callback(entries);
}
else {
entries.forEach(function(obj) {
console.log(obj);
});
}
});
};
/**
* LokiCatalog - underlying App/Key/Value catalog persistence
* This non-interface class implements the actual persistence.
* Used by the IndexedAdapter class.
*/
function LokiCatalog(callback)
{
this.db = null;
this.initializeLokiCatalog(callback);
}
LokiCatalog.prototype.initializeLokiCatalog = function(callback) {
var openRequest = indexedDB.open('LokiCatalog', 1);
var cat = this;
// If database doesn't exist yet or its version is lower than our version specified above (2nd param in line above)
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
if (thisDB.objectStoreNames.contains('LokiAKV')) {
thisDB.deleteObjectStore('LokiAKV');
}
if(!thisDB.objectStoreNames.contains('LokiAKV')) {
var objectStore = thisDB.createObjectStore('LokiAKV', { keyPath: 'id', autoIncrement:true });
objectStore.createIndex('app', 'app', {unique:false});
objectStore.createIndex('key', 'key', {unique:false});
// hack to simulate composite key since overhead is low (main size should be in val field)
// user (me) required to duplicate the app and key into comma delimited appkey field off object
// This will allow retrieving single record with that composite key as well as
// still supporting opening cursors on app or key alone
objectStore.createIndex('appkey', 'appkey', {unique:true});
}
};
openRequest.onsuccess = function(e) {
cat.db = e.target.result;
if (typeof (callback) === 'function') callback(cat);
};
openRequest.onerror = function(e) {
throw e;
};
};
LokiCatalog.prototype.getAppKey = function(app, key, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var index = store.index('appkey');
var appkey = app + "," + key;
var request = index.get(appkey);
request.onsuccess = (function(usercallback) {
return function(e) {
var lres = e.target.result;
if (lres === null || typeof(lres) === 'undefined') {
lres = {
id: 0,
success: false
};
}
if (typeof(usercallback) === 'function') {
usercallback(lres);
}
else {
console.log(lres);
}
};
})(callback);
request.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ id: 0, success: false });
}
else {
throw e;
}
};
})(callback);
};
LokiCatalog.prototype.getAppKeyById = function (id, callback, data) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var request = store.get(id);
request.onsuccess = (function(data, usercallback){
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback(e.target.result, data);
}
else {
console.log(e.target.result);
}
};
})(data, callback);
};
LokiCatalog.prototype.setAppKey = function (app, key, val, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
var store = transaction.objectStore('LokiAKV');
var index = store.index('appkey');
var appkey = app + "," + key;
var request = index.get(appkey);
// first try to retrieve an existing object by that key
// need to do this because to update an object you need to have id in object, otherwise it will append id with new autocounter and clash the unique index appkey
request.onsuccess = function(e) {
var res = e.target.result;
if (res === null || res === undefined) {
res = {
app:app,
key:key,
appkey: app + ',' + key,
val:val
};
}
else {
res.val = val;
}
var requestPut = store.put(res);
requestPut.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.setAppKey (set) onerror');
console.error(request.error);
}
};
})(callback);
requestPut.onsuccess = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: true });
}
};
})(callback);
};
request.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.setAppKey (get) onerror');
console.error(request.error);
}
};
})(callback);
};
LokiCatalog.prototype.deleteAppKey = function (id, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
var store = transaction.objectStore('LokiAKV');
var request = store.delete(id);
request.onsuccess = (function(usercallback) {
return function(evt) {
if (typeof(usercallback) === 'function') usercallback({ success: true });
};
})(callback);
request.onerror = (function(usercallback) {
return function(evt) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.deleteAppKey raised onerror');
console.error(request.error);
}
};
})(callback);
};
LokiCatalog.prototype.getAppKeys = function(app, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var index = store.index('app');
// We want cursor to all values matching our (single) app param
var singleKeyRange = IDBKeyRange.only(app);
// To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
var cursor = index.openCursor(singleKeyRange);
// cursor internally, pushing results into this.data[] and return
// this.data[] when done (similar to service)
var localdata = [];
cursor.onsuccess =