From 525ad16bfe0458fd2785c8d3068184ec978efb14 Mon Sep 17 00:00:00 2001 From: lieu Date: Sun, 6 Aug 2017 14:38:55 +0800 Subject: [PATCH] Add save function --- dist/package.json | 6 +- dist/renderer.js | 2844 +++++++++++++++++++++++++++++++++++++++++---- package.json | 5 +- src/index.js | 87 +- yarn.lock | 138 ++- 5 files changed, 2838 insertions(+), 242 deletions(-) diff --git a/dist/package.json b/dist/package.json index 9ff0671..99610f1 100644 --- a/dist/package.json +++ b/dist/package.json @@ -1,15 +1,17 @@ { - "name": "surveyor", - "version": "1.0.0", + "name": "inspector's gadget", + "version": "0.2.0", "description": "A report generation tool for civil engineers", "main": "main.js", "dependencies": { "css-loader": "^0.28.4", "electron-canvas-to-buffer": "^2.0.0", + "localforage": "^1.5.0", "lodash": "^4.17.4", "mathjs": "^3.14.2", "style-loader": "^0.18.2", "vex-js": "^4.0.0", + "vue": "^2.4.1", "webpack": "^3.1.0" }, "devDependencies": { diff --git a/dist/renderer.js b/dist/renderer.js index 08e01d7..c3c8544 100644 --- a/dist/renderer.js +++ b/dist/renderer.js @@ -1500,7 +1500,7 @@ module.exports = DimensionError; exports.array = __webpack_require__(3); -exports['boolean'] = __webpack_require__(49); +exports['boolean'] = __webpack_require__(50); exports['function'] = __webpack_require__(13); exports.number = __webpack_require__(2); exports.object = __webpack_require__(1); @@ -1777,7 +1777,7 @@ exports.factory = factory; var formatNumber = __webpack_require__(2).format; -var formatBigNumber = __webpack_require__(48).format; +var formatBigNumber = __webpack_require__(49).format; /** * Test whether value is a string @@ -2209,7 +2209,7 @@ var singleton = null; var singletonCounter = 0; var stylesInsertedAtTop = []; -var fixUrls = __webpack_require__(91); +var fixUrls = __webpack_require__(92); module.exports = function(list, options) { if (typeof DEBUG !== "undefined" && DEBUG) { @@ -2778,7 +2778,7 @@ function factory (type, config, load, typed) { var latex = __webpack_require__(6); var algorithm01 = load(__webpack_require__(30)); - var algorithm04 = load(__webpack_require__(52)); + var algorithm04 = load(__webpack_require__(53)); var algorithm10 = load(__webpack_require__(31)); var algorithm13 = load(__webpack_require__(10)); var algorithm14 = load(__webpack_require__(8)); @@ -3141,7 +3141,7 @@ function factory (type, config, load, typed) { var multiplyScalar = load(__webpack_require__(24)); var equalScalar = load(__webpack_require__(7)); - var algorithm11 = load(__webpack_require__(64)); + var algorithm11 = load(__webpack_require__(65)); var algorithm14 = load(__webpack_require__(8)); var DenseMatrix = type.DenseMatrix; @@ -4548,7 +4548,7 @@ exports.factory = factory; "use strict"; -var deepMap = __webpack_require__(63); +var deepMap = __webpack_require__(64); function factory (type, config, load, typed) { var latex = __webpack_require__(6); @@ -4875,7 +4875,7 @@ exports.factory = factory; /* 26 */ /***/ (function(module, exports, __webpack_require__) { -var Emitter = __webpack_require__(44); +var Emitter = __webpack_require__(45); /** * Extend given object with emitter functions `on`, `off`, `once`, `emit` @@ -7165,38 +7165,41 @@ module.exports = require("electron"); "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_mathjs_core__ = __webpack_require__(40); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_mathjs_core___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_mathjs_core__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__styles_style_css__ = __webpack_require__(89); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__styles_style_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1__styles_style_css__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__styles_tag_tables_css__ = __webpack_require__(92); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__styles_tag_tables_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__styles_tag_tables_css__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__styles_export_table_css__ = __webpack_require__(94); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__styles_export_table_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_3__styles_export_table_css__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_vex_css__ = __webpack_require__(96); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_vex_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__styles_vex_css__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_vex_theme_flat_attack_css__ = __webpack_require__(98); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_vex_theme_flat_attack_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5__styles_vex_theme_flat_attack_css__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_vex_js__ = __webpack_require__(100); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_vex_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_vex_js__); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_vue__ = __webpack_require__(103); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_localforage__ = __webpack_require__(40); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_localforage___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_localforage__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_mathjs_core__ = __webpack_require__(41); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_mathjs_core___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_mathjs_core__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__styles_style_css__ = __webpack_require__(90); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__styles_style_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__styles_style_css__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__styles_tag_tables_css__ = __webpack_require__(93); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__styles_tag_tables_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_3__styles_tag_tables_css__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_export_table_css__ = __webpack_require__(95); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__styles_export_table_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__styles_export_table_css__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_vex_css__ = __webpack_require__(97); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__styles_vex_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5__styles_vex_css__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__styles_vex_theme_flat_attack_css__ = __webpack_require__(99); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__styles_vex_theme_flat_attack_css___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6__styles_vex_theme_flat_attack_css__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_vex_js__ = __webpack_require__(101); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_vex_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_7_vex_js__); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8_vue__ = __webpack_require__(104); /*jshint esversion: 6 */ /* jshint node: true */ -let math = __WEBPACK_IMPORTED_MODULE_0_mathjs_core___default.a.create(); -math.import(__webpack_require__(47)); -math.import(__webpack_require__(60)); +let math = __WEBPACK_IMPORTED_MODULE_1_mathjs_core___default.a.create(); +math.import(__webpack_require__(48)); +math.import(__webpack_require__(61)); - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.registerPlugin(__webpack_require__(101)); -__WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.defaultOptions.className = 'vex-theme-flat-attack'; + + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.registerPlugin(__webpack_require__(102)); +__WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.defaultOptions.className = 'vex-theme-flat-attack'; const _ = { find_id: function(id) { @@ -7209,15 +7212,15 @@ const _ = { } }; -const canvasBuffer = __webpack_require__(102); +const canvasBuffer = __webpack_require__(103); const electron = __webpack_require__(38); // My own imports -const Canvas_Helper = __webpack_require__(104); -const jsPDF = __webpack_require__(105); -const html2canvas = __webpack_require__(108); -const html2pdf = __webpack_require__(109); +const Canvas_Helper = __webpack_require__(105); +const jsPDF = __webpack_require__(106); +const html2canvas = __webpack_require__(109); +const html2pdf = __webpack_require__(110); const htmlpdf = html2pdf(html2canvas, jsPDF); class Label { @@ -7242,13 +7245,14 @@ class Label { let globals = { BUILDING : '', FLOOR : '', + PLAN: '', KEYS : [], LABELS : [], ID : 1, CVS : null }; -let vue = new __WEBPACK_IMPORTED_MODULE_7_vue__["a" /* default */]({ +let vue = new __WEBPACK_IMPORTED_MODULE_8_vue__["a" /* default */]({ el: '.wrapper', data: { seen: true, @@ -7347,6 +7351,8 @@ function init() { canvas.width = 800; canvas.height = 566; } + //redraw the canvas + globals.CVS.draw_canvas(); }) let img = new Image(); img.src = './assets/canvas_placeholder.png'; @@ -7369,16 +7375,23 @@ function init() { else if (e.keyCode === 9 && globals.KEYS[17]) { //Control-Tab vue.toggle(); } + else if (e.keyCode === 83 && globals.KEYS[17]) { + console.log('calling'); + save_data(); + } + else if (e.keyCode === 76 && globals.KEYS[17]) { + load_data(); + } }; // Handling the reply when we send the exported floor plan electron.ipcRenderer.on('export_image', (e, args) => { - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.dialog.alert(args); + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.dialog.alert(args); }) } function edit_name(e) { - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.dialog.open({ + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.dialog.open({ message: 'Enter new name for label:', input: '', callback: (val) => { @@ -7441,7 +7454,7 @@ function handle_mousedown(evt) { // If right click, go to tag editing mode if (evt.button === 2 || evt.shiftKey) { - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.dialog.prompt({ + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.dialog.prompt({ message: 'Enter ID of label: ', callback: (value) => { let label = _.find_id(parseInt(value)); @@ -7511,26 +7524,33 @@ function handle_mousedown(evt) { } function upload_plan(file_list) { - const plan_img = file_list[0]; + const plan = file_list[0]; + const reader = new FileReader(); + /* let img = new Image(); img.onload = () => { - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.dialog.open({ + vex.dialog.open({ message: 'Enter building letter and floor', input: [ "", "", ].join(''), callback: (data) => { - console.log(data); - globals.CVS.image = img - if (data === undefined || data === false) { - globals.BUILDING = 'A'; + globals.CVS.image = img; + console.log(data, data.letter, data.floor); + if (data.letter === undefined) { + globals.BUILDING ='A'; + } + if (data.floor === undefined) { globals.FLOOR = '1'; } else { globals.BUILDING = data.letter; globals.FLOOR = data.floor; } + globals.PLAN = img; + console.log(globals.PLAN); + console.log(globals.CVS.image); update_labels(globals.LABELS); globals.CVS.draw_canvas(); //draw_table(globals.LABELS); @@ -7538,6 +7558,36 @@ function upload_plan(file_list) { }) }; img.src = URL.createObjectURL(plan_img); + */ + let img = new Image(); + reader.addEventListener('load', () => { + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.dialog.open({ + message: 'Enter building letter and floor', + input: [ + "", + "", + ].join(''), + callback: (data) => { + console.log(data, data.letter, data.floor); + if (data.letter === undefined) { + globals.BUILDING ='A'; + } + if (data.floor === undefined) { + globals.FLOOR = '1'; + } + else { + globals.BUILDING = data.letter; + globals.FLOOR = data.floor; + } + img.src = reader.result; + globals.CVS.image = img; + globals.PLAN = reader.result; + console.log(typeof(reader.result)); + globals.CVS.draw_canvas(); + } + }); + }); + reader.readAsDataURL(plan); } function upload_images(file_list) { @@ -7559,9 +7609,6 @@ function upload_images(file_list) { )); globals.ID++; globals.CVS.draw_canvas(); - //draw_table(globals.LABELS); - - console.log(i); if (i === file_list.length-1) { // All images have uploaded // Sort all images in name order globals.LABELS = globals.LABELS.sort( (a,b) => { @@ -7575,111 +7622,2472 @@ function upload_images(file_list) { return 0; } }); - // Reassign labels to match sort order - for (let j = 0; j < globals.LABELS.length; j++) { - globals.LABELS[j].id = j+1; + // Reassign labels to match sort order + for (let j = 0; j < globals.LABELS.length; j++) { + globals.LABELS[j].id = j+1; + } + update_labels(globals.LABELS); + } + + }); + reader.readAsDataURL(file); + } +} + +function update_labels(labels) { + labels.map( (label) => { + label.title = `${globals.BUILDING}${globals.FLOOR}-${label.id}`; + }) +} +function export_image(e) { + const export_canvas = document.createElement('canvas'); + export_canvas.height = 3000; + export_canvas.width = Math.round(export_canvas.height * Math.sqrt(2)); + const ctx = export_canvas.getContext('2d'); + ctx.clearRect(0, 0, export_canvas.height, export_canvas.width); + + // Rescale image such that the largest dimension of the image fits nicely + const working_height = export_canvas.height - 200; + const working_width = export_canvas.width - 200; + const max_x = globals.CVS.image.width; + const max_y = globals.CVS.image.height; + let ratio = 1; + + let xratio = working_width / max_x; + let yratio = working_height / max_y; + ratio = Math.min(xratio, yratio); + + const x_offset = (export_canvas.width - max_x * ratio) / 2; + const y_offset = (export_canvas.height - max_y * ratio) / 2; + + // make a deep copy of LABELS so as not to mutate it + let temp_labels = JSON.parse(JSON.stringify(globals.LABELS)); + temp_labels.map( (label) => { + label.x = label.x * ratio + x_offset; + label.y = label.y * ratio + y_offset; + }); + ctx.drawImage(globals.CVS.image, 0, 0, globals.CVS.image.width, globals.CVS.image.height, + x_offset, y_offset, globals.CVS.image.width * ratio, + globals.CVS.image.height * ratio); + + // These ratios are how much to scale the labels and overlay to the enlarged + // canvas + let x_ratio = working_height / globals.CVS.image.height; + let y_ratio = working_width / globals.CVS.image.width; + let draw_ratio = Math.min(x_ratio, y_ratio); + draw_ratio = 1; + + temp_labels.map( (label) => { globals.CVS.draw_label(label, ctx, draw_ratio); }); + __WEBPACK_IMPORTED_MODULE_7_vex_js___default.a.dialog.open({ + message: 'Enter building overlay text', + input: "", + callback: (text) => { + if (text === undefined) { + globals.CVS.draw_overlay(export_canvas, ctx); + } + else { + globals.CVS.draw_overlay(export_canvas, ctx, text.letter); + } + let buffer = canvasBuffer(export_canvas, 'image/png'); + electron.remote.getGlobal('data').exportedImage = buffer; + electron.ipcRenderer.send('export_image', buffer); + export_canvas.remove(); // Garbage collection + } + }); +} + +function save_data() { + const reader = new FileReader(); + electron.ipcRenderer.send('save_data', globals); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.setItem('building', globals.BUILDING) + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.setItem('floor', globals.FLOOR); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.setItem('labels', globals.LABELS); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.setItem('id', globals.ID); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.setItem('plan', globals.PLAN); +} + +function load_data() { + console.log('loading'); + electron.ipcRenderer.send('load_data', globals); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.iterate((value, key, iterNo) => {console.log([key, value])}); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.getItem('building').then((value) => { + globals['BUILDING'] = value; + }); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.getItem('floor').then((value) => { + globals['FLOOR'] = value; + }); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.getItem('plan').then((value) => { + globals['PLAN'] = value; + // convert dataURL to HTMLImageElement so that canvas can draw it + let img = new Image(); + img.src = globals.PLAN; + globals.CVS.image = img; + }); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.getItem('labels').then( (labels) => { + globals.LABELS = []; // clear labels + labels.forEach((l) => { + globals.LABELS.push(new Label(l.id, l.x, l.y, + l.title, l.caption, l.defect, + l.image_src, l.image + ) + ) + } + ) + vue._data.labels = globals.LABELS; + }); + __WEBPACK_IMPORTED_MODULE_0_localforage___default.a.getItem('id').then( (value) => { + globals['ID'] = value; + }); +} + +function export_table() { + var element = document.getElementById('export-table'); + htmlpdf(element, { + margin: 0, + filename: 'export.pdf', + image: { type: 'jpeg', quality: 0.98 }, + html2canvas: { dpi: 192, letterRendering: true }, + jsPDF: { unit: 'in', format: 'A4', orientation: 'portrait' } + }); +} + +init(); + + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + +var require;var require;/*! + localForage -- Offline Storage, Improved + Version 1.5.0 + https://localforage.github.io/localForage + (c) 2013-2017 Mozilla, Apache License 2.0 +*/ +(function(f){if(true){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.localforage = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return require(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw (f.code="MODULE_NOT_FOUND", f)}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o element; its readystatechange event will be fired asynchronously once it is inserted + // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called. + var scriptEl = global.document.createElement('script'); + scriptEl.onreadystatechange = function () { + nextTick(); + + scriptEl.onreadystatechange = null; + scriptEl.parentNode.removeChild(scriptEl); + scriptEl = null; + }; + global.document.documentElement.appendChild(scriptEl); + }; + } else { + scheduleDrain = function () { + setTimeout(nextTick, 0); + }; + } +} + +var draining; +var queue = []; +//named nextTick for less confusing stack traces +function nextTick() { + draining = true; + var i, oldQueue; + var len = queue.length; + while (len) { + oldQueue = queue; + queue = []; + i = -1; + while (++i < len) { + oldQueue[i](); + } + len = queue.length; + } + draining = false; +} + +module.exports = immediate; +function immediate(task) { + if (queue.push(task) === 1 && !draining) { + scheduleDrain(); + } +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],2:[function(_dereq_,module,exports){ +'use strict'; +var immediate = _dereq_(1); + +/* istanbul ignore next */ +function INTERNAL() {} + +var handlers = {}; + +var REJECTED = ['REJECTED']; +var FULFILLED = ['FULFILLED']; +var PENDING = ['PENDING']; + +module.exports = exports = Promise; + +function Promise(resolver) { + if (typeof resolver !== 'function') { + throw new TypeError('resolver must be a function'); + } + this.state = PENDING; + this.queue = []; + this.outcome = void 0; + if (resolver !== INTERNAL) { + safelyResolveThenable(this, resolver); + } +} + +Promise.prototype["catch"] = function (onRejected) { + return this.then(null, onRejected); +}; +Promise.prototype.then = function (onFulfilled, onRejected) { + if (typeof onFulfilled !== 'function' && this.state === FULFILLED || + typeof onRejected !== 'function' && this.state === REJECTED) { + return this; + } + var promise = new this.constructor(INTERNAL); + if (this.state !== PENDING) { + var resolver = this.state === FULFILLED ? onFulfilled : onRejected; + unwrap(promise, resolver, this.outcome); + } else { + this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); + } + + return promise; +}; +function QueueItem(promise, onFulfilled, onRejected) { + this.promise = promise; + if (typeof onFulfilled === 'function') { + this.onFulfilled = onFulfilled; + this.callFulfilled = this.otherCallFulfilled; + } + if (typeof onRejected === 'function') { + this.onRejected = onRejected; + this.callRejected = this.otherCallRejected; + } +} +QueueItem.prototype.callFulfilled = function (value) { + handlers.resolve(this.promise, value); +}; +QueueItem.prototype.otherCallFulfilled = function (value) { + unwrap(this.promise, this.onFulfilled, value); +}; +QueueItem.prototype.callRejected = function (value) { + handlers.reject(this.promise, value); +}; +QueueItem.prototype.otherCallRejected = function (value) { + unwrap(this.promise, this.onRejected, value); +}; + +function unwrap(promise, func, value) { + immediate(function () { + var returnValue; + try { + returnValue = func(value); + } catch (e) { + return handlers.reject(promise, e); + } + if (returnValue === promise) { + handlers.reject(promise, new TypeError('Cannot resolve promise with itself')); + } else { + handlers.resolve(promise, returnValue); + } + }); +} + +handlers.resolve = function (self, value) { + var result = tryCatch(getThen, value); + if (result.status === 'error') { + return handlers.reject(self, result.value); + } + var thenable = result.value; + + if (thenable) { + safelyResolveThenable(self, thenable); + } else { + self.state = FULFILLED; + self.outcome = value; + var i = -1; + var len = self.queue.length; + while (++i < len) { + self.queue[i].callFulfilled(value); + } + } + return self; +}; +handlers.reject = function (self, error) { + self.state = REJECTED; + self.outcome = error; + var i = -1; + var len = self.queue.length; + while (++i < len) { + self.queue[i].callRejected(error); + } + return self; +}; + +function getThen(obj) { + // Make sure we only access the accessor once as required by the spec + var then = obj && obj.then; + if (obj && typeof obj === 'object' && typeof then === 'function') { + return function appyThen() { + then.apply(obj, arguments); + }; + } +} + +function safelyResolveThenable(self, thenable) { + // Either fulfill, reject or reject with error + var called = false; + function onError(value) { + if (called) { + return; + } + called = true; + handlers.reject(self, value); + } + + function onSuccess(value) { + if (called) { + return; + } + called = true; + handlers.resolve(self, value); + } + + function tryToUnwrap() { + thenable(onSuccess, onError); + } + + var result = tryCatch(tryToUnwrap); + if (result.status === 'error') { + onError(result.value); + } +} + +function tryCatch(func, value) { + var out = {}; + try { + out.value = func(value); + out.status = 'success'; + } catch (e) { + out.status = 'error'; + out.value = e; + } + return out; +} + +exports.resolve = resolve; +function resolve(value) { + if (value instanceof this) { + return value; + } + return handlers.resolve(new this(INTERNAL), value); +} + +exports.reject = reject; +function reject(reason) { + var promise = new this(INTERNAL); + return handlers.reject(promise, reason); +} + +exports.all = all; +function all(iterable) { + var self = this; + if (Object.prototype.toString.call(iterable) !== '[object Array]') { + return this.reject(new TypeError('must be an array')); + } + + var len = iterable.length; + var called = false; + if (!len) { + return this.resolve([]); + } + + var values = new Array(len); + var resolved = 0; + var i = -1; + var promise = new this(INTERNAL); + + while (++i < len) { + allResolver(iterable[i], i); + } + return promise; + function allResolver(value, i) { + self.resolve(value).then(resolveFromAll, function (error) { + if (!called) { + called = true; + handlers.reject(promise, error); + } + }); + function resolveFromAll(outValue) { + values[i] = outValue; + if (++resolved === len && !called) { + called = true; + handlers.resolve(promise, values); + } + } + } +} + +exports.race = race; +function race(iterable) { + var self = this; + if (Object.prototype.toString.call(iterable) !== '[object Array]') { + return this.reject(new TypeError('must be an array')); + } + + var len = iterable.length; + var called = false; + if (!len) { + return this.resolve([]); + } + + var i = -1; + var promise = new this(INTERNAL); + + while (++i < len) { + resolver(iterable[i]); + } + return promise; + function resolver(value) { + self.resolve(value).then(function (response) { + if (!called) { + called = true; + handlers.resolve(promise, response); + } + }, function (error) { + if (!called) { + called = true; + handlers.reject(promise, error); + } + }); + } +} + +},{"1":1}],3:[function(_dereq_,module,exports){ +(function (global){ +'use strict'; +if (typeof global.Promise !== 'function') { + global.Promise = _dereq_(2); +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"2":2}],4:[function(_dereq_,module,exports){ +'use strict'; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function getIDB() { + /* global indexedDB,webkitIndexedDB,mozIndexedDB,OIndexedDB,msIndexedDB */ + try { + if (typeof indexedDB !== 'undefined') { + return indexedDB; + } + if (typeof webkitIndexedDB !== 'undefined') { + return webkitIndexedDB; + } + if (typeof mozIndexedDB !== 'undefined') { + return mozIndexedDB; + } + if (typeof OIndexedDB !== 'undefined') { + return OIndexedDB; + } + if (typeof msIndexedDB !== 'undefined') { + return msIndexedDB; + } + } catch (e) {} +} + +var idb = getIDB(); + +function isIndexedDBValid() { + try { + // Initialize IndexedDB; fall back to vendor-prefixed versions + // if needed. + if (!idb) { + return false; + } + // We mimic PouchDB here; + // + // We test for openDatabase because IE Mobile identifies itself + // as Safari. Oh the lulz... + var isSafari = typeof openDatabase !== 'undefined' && /(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) && !/BlackBerry/.test(navigator.platform); + + var hasFetch = typeof fetch === 'function' && fetch.toString().indexOf('[native code') !== -1; + + // Safari <10.1 does not meet our requirements for IDB support (#5572) + // since Safari 10.1 shipped with fetch, we can use that to detect it + return (!isSafari || hasFetch) && typeof indexedDB !== 'undefined' && + // some outdated implementations of IDB that appear on Samsung + // and HTC Android devices <4.4 are missing IDBKeyRange + typeof IDBKeyRange !== 'undefined'; + } catch (e) { + return false; + } +} + +function isWebSQLValid() { + return typeof openDatabase === 'function'; +} + +function isLocalStorageValid() { + try { + return typeof localStorage !== 'undefined' && 'setItem' in localStorage && localStorage.setItem; + } catch (e) { + return false; + } +} + +// Abstracts constructing a Blob object, so it also works in older +// browsers that don't support the native Blob constructor. (i.e. +// old QtWebKit versions, at least). +// Abstracts constructing a Blob object, so it also works in older +// browsers that don't support the native Blob constructor. (i.e. +// old QtWebKit versions, at least). +function createBlob(parts, properties) { + /* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */ + parts = parts || []; + properties = properties || {}; + try { + return new Blob(parts, properties); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder : typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder : typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : WebKitBlobBuilder; + var builder = new Builder(); + for (var i = 0; i < parts.length; i += 1) { + builder.append(parts[i]); + } + return builder.getBlob(properties.type); + } +} + +// This is CommonJS because lie is an external dependency, so Rollup +// can just ignore it. +if (typeof Promise === 'undefined') { + // In the "nopromises" build this will just throw if you don't have + // a global promise object, but it would throw anyway later. + _dereq_(3); +} +var Promise$1 = Promise; + +function executeCallback(promise, callback) { + if (callback) { + promise.then(function (result) { + callback(null, result); + }, function (error) { + callback(error); + }); + } +} + +function executeTwoCallbacks(promise, callback, errorCallback) { + if (typeof callback === 'function') { + promise.then(callback); + } + + if (typeof errorCallback === 'function') { + promise["catch"](errorCallback); + } +} + +// Some code originally from async_storage.js in +// [Gaia](https://github.com/mozilla-b2g/gaia). + +var DETECT_BLOB_SUPPORT_STORE = 'local-forage-detect-blob-support'; +var supportsBlobs; +var dbContexts; +var toString = Object.prototype.toString; + +// Transform a binary string to an array buffer, because otherwise +// weird stuff happens when you try to work with the binary string directly. +// It is known. +// From http://stackoverflow.com/questions/14967647/ (continues on next line) +// encode-decode-image-with-base64-breaks-image (2013-04-21) +function _binStringToArrayBuffer(bin) { + var length = bin.length; + var buf = new ArrayBuffer(length); + var arr = new Uint8Array(buf); + for (var i = 0; i < length; i++) { + arr[i] = bin.charCodeAt(i); + } + return buf; +} + +// +// Blobs are not supported in all versions of IndexedDB, notably +// Chrome <37 and Android <5. In those versions, storing a blob will throw. +// +// Various other blob bugs exist in Chrome v37-42 (inclusive). +// Detecting them is expensive and confusing to users, and Chrome 37-42 +// is at very low usage worldwide, so we do a hacky userAgent check instead. +// +// content-type bug: https://code.google.com/p/chromium/issues/detail?id=408120 +// 404 bug: https://code.google.com/p/chromium/issues/detail?id=447916 +// FileReader bug: https://code.google.com/p/chromium/issues/detail?id=447836 +// +// Code borrowed from PouchDB. See: +// https://github.com/pouchdb/pouchdb/blob/master/packages/node_modules/pouchdb-adapter-idb/src/blobSupport.js +// +function _checkBlobSupportWithoutCaching(idb) { + return new Promise$1(function (resolve) { + var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite'); + var blob = createBlob(['']); + txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key'); + + txn.onabort = function (e) { + // If the transaction aborts now its due to not being able to + // write to the database, likely due to the disk being full + e.preventDefault(); + e.stopPropagation(); + resolve(false); + }; + + txn.oncomplete = function () { + var matchedChrome = navigator.userAgent.match(/Chrome\/(\d+)/); + var matchedEdge = navigator.userAgent.match(/Edge\//); + // MS Edge pretends to be Chrome 42: + // https://msdn.microsoft.com/en-us/library/hh869301%28v=vs.85%29.aspx + resolve(matchedEdge || !matchedChrome || parseInt(matchedChrome[1], 10) >= 43); + }; + })["catch"](function () { + return false; // error, so assume unsupported + }); +} + +function _checkBlobSupport(idb) { + if (typeof supportsBlobs === 'boolean') { + return Promise$1.resolve(supportsBlobs); + } + return _checkBlobSupportWithoutCaching(idb).then(function (value) { + supportsBlobs = value; + return supportsBlobs; + }); +} + +function _deferReadiness(dbInfo) { + var dbContext = dbContexts[dbInfo.name]; + + // Create a deferred object representing the current database operation. + var deferredOperation = {}; + + deferredOperation.promise = new Promise$1(function (resolve) { + deferredOperation.resolve = resolve; + }); + + // Enqueue the deferred operation. + dbContext.deferredOperations.push(deferredOperation); + + // Chain its promise to the database readiness. + if (!dbContext.dbReady) { + dbContext.dbReady = deferredOperation.promise; + } else { + dbContext.dbReady = dbContext.dbReady.then(function () { + return deferredOperation.promise; + }); + } +} + +function _advanceReadiness(dbInfo) { + var dbContext = dbContexts[dbInfo.name]; + + // Dequeue a deferred operation. + var deferredOperation = dbContext.deferredOperations.pop(); + + // Resolve its promise (which is part of the database readiness + // chain of promises). + if (deferredOperation) { + deferredOperation.resolve(); + } +} + +function _getConnection(dbInfo, upgradeNeeded) { + return new Promise$1(function (resolve, reject) { + + if (dbInfo.db) { + if (upgradeNeeded) { + _deferReadiness(dbInfo); + dbInfo.db.close(); + } else { + return resolve(dbInfo.db); + } + } + + var dbArgs = [dbInfo.name]; + + if (upgradeNeeded) { + dbArgs.push(dbInfo.version); + } + + var openreq = idb.open.apply(idb, dbArgs); + + if (upgradeNeeded) { + openreq.onupgradeneeded = function (e) { + var db = openreq.result; + try { + db.createObjectStore(dbInfo.storeName); + if (e.oldVersion <= 1) { + // Added when support for blob shims was added + db.createObjectStore(DETECT_BLOB_SUPPORT_STORE); + } + } catch (ex) { + if (ex.name === 'ConstraintError') { + console.warn('The database "' + dbInfo.name + '"' + ' has been upgraded from version ' + e.oldVersion + ' to version ' + e.newVersion + ', but the storage "' + dbInfo.storeName + '" already exists.'); + } else { + throw ex; + } + } + }; + } + + openreq.onerror = function (e) { + e.preventDefault(); + reject(openreq.error); + }; + + openreq.onsuccess = function () { + resolve(openreq.result); + _advanceReadiness(dbInfo); + }; + }); +} + +function _getOriginalConnection(dbInfo) { + return _getConnection(dbInfo, false); +} + +function _getUpgradedConnection(dbInfo) { + return _getConnection(dbInfo, true); +} + +function _isUpgradeNeeded(dbInfo, defaultVersion) { + if (!dbInfo.db) { + return true; + } + + var isNewStore = !dbInfo.db.objectStoreNames.contains(dbInfo.storeName); + var isDowngrade = dbInfo.version < dbInfo.db.version; + var isUpgrade = dbInfo.version > dbInfo.db.version; + + if (isDowngrade) { + // If the version is not the default one + // then warn for impossible downgrade. + if (dbInfo.version !== defaultVersion) { + console.warn('The database "' + dbInfo.name + '"' + ' can\'t be downgraded from version ' + dbInfo.db.version + ' to version ' + dbInfo.version + '.'); + } + // Align the versions to prevent errors. + dbInfo.version = dbInfo.db.version; + } + + if (isUpgrade || isNewStore) { + // If the store is new then increment the version (if needed). + // This will trigger an "upgradeneeded" event which is required + // for creating a store. + if (isNewStore) { + var incVersion = dbInfo.db.version + 1; + if (incVersion > dbInfo.version) { + dbInfo.version = incVersion; + } + } + + return true; + } + + return false; +} + +// encode a blob for indexeddb engines that don't support blobs +function _encodeBlob(blob) { + return new Promise$1(function (resolve, reject) { + var reader = new FileReader(); + reader.onerror = reject; + reader.onloadend = function (e) { + var base64 = btoa(e.target.result || ''); + resolve({ + __local_forage_encoded_blob: true, + data: base64, + type: blob.type + }); + }; + reader.readAsBinaryString(blob); + }); +} + +// decode an encoded blob +function _decodeBlob(encodedBlob) { + var arrayBuff = _binStringToArrayBuffer(atob(encodedBlob.data)); + return createBlob([arrayBuff], { type: encodedBlob.type }); +} + +// is this one of our fancy encoded blobs? +function _isEncodedBlob(value) { + return value && value.__local_forage_encoded_blob; +} + +// Specialize the default `ready()` function by making it dependent +// on the current database operations. Thus, the driver will be actually +// ready when it's been initialized (default) *and* there are no pending +// operations on the database (initiated by some other instances). +function _fullyReady(callback) { + var self = this; + + var promise = self._initReady().then(function () { + var dbContext = dbContexts[self._dbInfo.name]; + + if (dbContext && dbContext.dbReady) { + return dbContext.dbReady; + } + }); + + executeTwoCallbacks(promise, callback, callback); + return promise; +} + +// Open the IndexedDB database (automatically creates one if one didn't +// previously exist), using any options set in the config. +function _initStorage(options) { + var self = this; + var dbInfo = { + db: null + }; + + if (options) { + for (var i in options) { + dbInfo[i] = options[i]; + } + } + + // Initialize a singleton container for all running localForages. + if (!dbContexts) { + dbContexts = {}; + } + + // Get the current context of the database; + var dbContext = dbContexts[dbInfo.name]; + + // ...or create a new context. + if (!dbContext) { + dbContext = { + // Running localForages sharing a database. + forages: [], + // Shared database. + db: null, + // Database readiness (promise). + dbReady: null, + // Deferred operations on the database. + deferredOperations: [] + }; + // Register the new context in the global container. + dbContexts[dbInfo.name] = dbContext; + } + + // Register itself as a running localForage in the current context. + dbContext.forages.push(self); + + // Replace the default `ready()` function with the specialized one. + if (!self._initReady) { + self._initReady = self.ready; + self.ready = _fullyReady; + } + + // Create an array of initialization states of the related localForages. + var initPromises = []; + + function ignoreErrors() { + // Don't handle errors here, + // just makes sure related localForages aren't pending. + return Promise$1.resolve(); + } + + for (var j = 0; j < dbContext.forages.length; j++) { + var forage = dbContext.forages[j]; + if (forage !== self) { + // Don't wait for itself... + initPromises.push(forage._initReady()["catch"](ignoreErrors)); + } + } + + // Take a snapshot of the related localForages. + var forages = dbContext.forages.slice(0); + + // Initialize the connection process only when + // all the related localForages aren't pending. + return Promise$1.all(initPromises).then(function () { + dbInfo.db = dbContext.db; + // Get the connection or open a new one without upgrade. + return _getOriginalConnection(dbInfo); + }).then(function (db) { + dbInfo.db = db; + if (_isUpgradeNeeded(dbInfo, self._defaultConfig.version)) { + // Reopen the database for upgrading. + return _getUpgradedConnection(dbInfo); + } + return db; + }).then(function (db) { + dbInfo.db = dbContext.db = db; + self._dbInfo = dbInfo; + // Share the final connection amongst related localForages. + for (var k = 0; k < forages.length; k++) { + var forage = forages[k]; + if (forage !== self) { + // Self is already up-to-date. + forage._dbInfo.db = dbInfo.db; + forage._dbInfo.version = dbInfo.version; + } + } + }); +} + +function getItem(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName); + var req = store.get(key); + + req.onsuccess = function () { + var value = req.result; + if (value === undefined) { + value = null; + } + if (_isEncodedBlob(value)) { + value = _decodeBlob(value); + } + resolve(value); + }; + + req.onerror = function () { + reject(req.error); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +// Iterate over all items stored in database. +function iterate(iterator, callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName); + + var req = store.openCursor(); + var iterationNumber = 1; + + req.onsuccess = function () { + var cursor = req.result; + + if (cursor) { + var value = cursor.value; + if (_isEncodedBlob(value)) { + value = _decodeBlob(value); + } + var result = iterator(value, cursor.key, iterationNumber++); + + if (result !== void 0) { + resolve(result); + } else { + cursor["continue"](); + } + } else { + resolve(); + } + }; + + req.onerror = function () { + reject(req.error); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + + return promise; +} + +function setItem(key, value, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + var dbInfo; + self.ready().then(function () { + dbInfo = self._dbInfo; + if (toString.call(value) === '[object Blob]') { + return _checkBlobSupport(dbInfo.db).then(function (blobSupport) { + if (blobSupport) { + return value; + } + return _encodeBlob(value); + }); + } + return value; + }).then(function (value) { + var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite'); + var store = transaction.objectStore(dbInfo.storeName); + var req = store.put(value, key); + + // The reason we don't _save_ null is because IE 10 does + // not support saving the `null` type in IndexedDB. How + // ironic, given the bug below! + // See: https://github.com/mozilla/localForage/issues/161 + if (value === null) { + value = undefined; + } + + transaction.oncomplete = function () { + // Cast to undefined so the value passed to + // callback/promise is the same as what one would get out + // of `getItem()` later. This leads to some weirdness + // (setItem('foo', undefined) will return `null`), but + // it's not my fault localStorage is our baseline and that + // it's weird. + if (value === undefined) { + value = null; + } + + resolve(value); + }; + transaction.onabort = transaction.onerror = function () { + var err = req.error ? req.error : req.transaction.error; + reject(err); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function removeItem(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite'); + var store = transaction.objectStore(dbInfo.storeName); + + // We use a Grunt task to make this safe for IE and some + // versions of Android (including those used by Cordova). + // Normally IE won't like `.delete()` and will insist on + // using `['delete']()`, but we have a build step that + // fixes this for us now. + var req = store["delete"](key); + transaction.oncomplete = function () { + resolve(); + }; + + transaction.onerror = function () { + reject(req.error); + }; + + // The request will be also be aborted if we've exceeded our storage + // space. + transaction.onabort = function () { + var err = req.error ? req.error : req.transaction.error; + reject(err); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function clear(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite'); + var store = transaction.objectStore(dbInfo.storeName); + var req = store.clear(); + + transaction.oncomplete = function () { + resolve(); + }; + + transaction.onabort = transaction.onerror = function () { + var err = req.error ? req.error : req.transaction.error; + reject(err); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function length(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName); + var req = store.count(); + + req.onsuccess = function () { + resolve(req.result); + }; + + req.onerror = function () { + reject(req.error); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function key(n, callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + if (n < 0) { + resolve(null); + + return; + } + + self.ready().then(function () { + var dbInfo = self._dbInfo; + var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName); + + var advanced = false; + var req = store.openCursor(); + req.onsuccess = function () { + var cursor = req.result; + if (!cursor) { + // this means there weren't enough keys + resolve(null); + + return; + } + + if (n === 0) { + // We have the first key, return it if that's what they + // wanted. + resolve(cursor.key); + } else { + if (!advanced) { + // Otherwise, ask the cursor to skip ahead n + // records. + advanced = true; + cursor.advance(n); + } else { + // When we get here, we've got the nth key. + resolve(cursor.key); + } + } + }; + + req.onerror = function () { + reject(req.error); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function keys(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName); + + var req = store.openCursor(); + var keys = []; + + req.onsuccess = function () { + var cursor = req.result; + + if (!cursor) { + resolve(keys); + return; + } + + keys.push(cursor.key); + cursor["continue"](); + }; + + req.onerror = function () { + reject(req.error); + }; + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +var asyncStorage = { + _driver: 'asyncStorage', + _initStorage: _initStorage, + iterate: iterate, + getItem: getItem, + setItem: setItem, + removeItem: removeItem, + clear: clear, + length: length, + key: key, + keys: keys +}; + +// Sadly, the best way to save binary data in WebSQL/localStorage is serializing +// it to Base64, so this is how we store it to prevent very strange errors with less +// verbose ways of binary <-> string data storage. +var BASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +var BLOB_TYPE_PREFIX = '~~local_forage_type~'; +var BLOB_TYPE_PREFIX_REGEX = /^~~local_forage_type~([^~]+)~/; + +var SERIALIZED_MARKER = '__lfsc__:'; +var SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER.length; + +// OMG the serializations! +var TYPE_ARRAYBUFFER = 'arbf'; +var TYPE_BLOB = 'blob'; +var TYPE_INT8ARRAY = 'si08'; +var TYPE_UINT8ARRAY = 'ui08'; +var TYPE_UINT8CLAMPEDARRAY = 'uic8'; +var TYPE_INT16ARRAY = 'si16'; +var TYPE_INT32ARRAY = 'si32'; +var TYPE_UINT16ARRAY = 'ur16'; +var TYPE_UINT32ARRAY = 'ui32'; +var TYPE_FLOAT32ARRAY = 'fl32'; +var TYPE_FLOAT64ARRAY = 'fl64'; +var TYPE_SERIALIZED_MARKER_LENGTH = SERIALIZED_MARKER_LENGTH + TYPE_ARRAYBUFFER.length; + +var toString$1 = Object.prototype.toString; + +function stringToBuffer(serializedString) { + // Fill the string into a ArrayBuffer. + var bufferLength = serializedString.length * 0.75; + var len = serializedString.length; + var i; + var p = 0; + var encoded1, encoded2, encoded3, encoded4; + + if (serializedString[serializedString.length - 1] === '=') { + bufferLength--; + if (serializedString[serializedString.length - 2] === '=') { + bufferLength--; + } + } + + var buffer = new ArrayBuffer(bufferLength); + var bytes = new Uint8Array(buffer); + + for (i = 0; i < len; i += 4) { + encoded1 = BASE_CHARS.indexOf(serializedString[i]); + encoded2 = BASE_CHARS.indexOf(serializedString[i + 1]); + encoded3 = BASE_CHARS.indexOf(serializedString[i + 2]); + encoded4 = BASE_CHARS.indexOf(serializedString[i + 3]); + + /*jslint bitwise: true */ + bytes[p++] = encoded1 << 2 | encoded2 >> 4; + bytes[p++] = (encoded2 & 15) << 4 | encoded3 >> 2; + bytes[p++] = (encoded3 & 3) << 6 | encoded4 & 63; + } + return buffer; +} + +// Converts a buffer to a string to store, serialized, in the backend +// storage library. +function bufferToString(buffer) { + // base64-arraybuffer + var bytes = new Uint8Array(buffer); + var base64String = ''; + var i; + + for (i = 0; i < bytes.length; i += 3) { + /*jslint bitwise: true */ + base64String += BASE_CHARS[bytes[i] >> 2]; + base64String += BASE_CHARS[(bytes[i] & 3) << 4 | bytes[i + 1] >> 4]; + base64String += BASE_CHARS[(bytes[i + 1] & 15) << 2 | bytes[i + 2] >> 6]; + base64String += BASE_CHARS[bytes[i + 2] & 63]; + } + + if (bytes.length % 3 === 2) { + base64String = base64String.substring(0, base64String.length - 1) + '='; + } else if (bytes.length % 3 === 1) { + base64String = base64String.substring(0, base64String.length - 2) + '=='; + } + + return base64String; +} + +// Serialize a value, afterwards executing a callback (which usually +// instructs the `setItem()` callback/promise to be executed). This is how +// we store binary data with localStorage. +function serialize(value, callback) { + var valueType = ''; + if (value) { + valueType = toString$1.call(value); + } + + // Cannot use `value instanceof ArrayBuffer` or such here, as these + // checks fail when running the tests using casper.js... + // + // TODO: See why those tests fail and use a better solution. + if (value && (valueType === '[object ArrayBuffer]' || value.buffer && toString$1.call(value.buffer) === '[object ArrayBuffer]')) { + // Convert binary arrays to a string and prefix the string with + // a special marker. + var buffer; + var marker = SERIALIZED_MARKER; + + if (value instanceof ArrayBuffer) { + buffer = value; + marker += TYPE_ARRAYBUFFER; + } else { + buffer = value.buffer; + + if (valueType === '[object Int8Array]') { + marker += TYPE_INT8ARRAY; + } else if (valueType === '[object Uint8Array]') { + marker += TYPE_UINT8ARRAY; + } else if (valueType === '[object Uint8ClampedArray]') { + marker += TYPE_UINT8CLAMPEDARRAY; + } else if (valueType === '[object Int16Array]') { + marker += TYPE_INT16ARRAY; + } else if (valueType === '[object Uint16Array]') { + marker += TYPE_UINT16ARRAY; + } else if (valueType === '[object Int32Array]') { + marker += TYPE_INT32ARRAY; + } else if (valueType === '[object Uint32Array]') { + marker += TYPE_UINT32ARRAY; + } else if (valueType === '[object Float32Array]') { + marker += TYPE_FLOAT32ARRAY; + } else if (valueType === '[object Float64Array]') { + marker += TYPE_FLOAT64ARRAY; + } else { + callback(new Error('Failed to get type for BinaryArray')); + } + } + + callback(marker + bufferToString(buffer)); + } else if (valueType === '[object Blob]') { + // Conver the blob to a binaryArray and then to a string. + var fileReader = new FileReader(); + + fileReader.onload = function () { + // Backwards-compatible prefix for the blob type. + var str = BLOB_TYPE_PREFIX + value.type + '~' + bufferToString(this.result); + + callback(SERIALIZED_MARKER + TYPE_BLOB + str); + }; + + fileReader.readAsArrayBuffer(value); + } else { + try { + callback(JSON.stringify(value)); + } catch (e) { + console.error("Couldn't convert value into a JSON string: ", value); + + callback(null, e); + } + } +} + +// Deserialize data we've inserted into a value column/field. We place +// special markers into our strings to mark them as encoded; this isn't +// as nice as a meta field, but it's the only sane thing we can do whilst +// keeping localStorage support intact. +// +// Oftentimes this will just deserialize JSON content, but if we have a +// special marker (SERIALIZED_MARKER, defined above), we will extract +// some kind of arraybuffer/binary data/typed array out of the string. +function deserialize(value) { + // If we haven't marked this string as being specially serialized (i.e. + // something other than serialized JSON), we can just return it and be + // done with it. + if (value.substring(0, SERIALIZED_MARKER_LENGTH) !== SERIALIZED_MARKER) { + return JSON.parse(value); + } + + // The following code deals with deserializing some kind of Blob or + // TypedArray. First we separate out the type of data we're dealing + // with from the data itself. + var serializedString = value.substring(TYPE_SERIALIZED_MARKER_LENGTH); + var type = value.substring(SERIALIZED_MARKER_LENGTH, TYPE_SERIALIZED_MARKER_LENGTH); + + var blobType; + // Backwards-compatible blob type serialization strategy. + // DBs created with older versions of localForage will simply not have the blob type. + if (type === TYPE_BLOB && BLOB_TYPE_PREFIX_REGEX.test(serializedString)) { + var matcher = serializedString.match(BLOB_TYPE_PREFIX_REGEX); + blobType = matcher[1]; + serializedString = serializedString.substring(matcher[0].length); + } + var buffer = stringToBuffer(serializedString); + + // Return the right type based on the code/type set during + // serialization. + switch (type) { + case TYPE_ARRAYBUFFER: + return buffer; + case TYPE_BLOB: + return createBlob([buffer], { type: blobType }); + case TYPE_INT8ARRAY: + return new Int8Array(buffer); + case TYPE_UINT8ARRAY: + return new Uint8Array(buffer); + case TYPE_UINT8CLAMPEDARRAY: + return new Uint8ClampedArray(buffer); + case TYPE_INT16ARRAY: + return new Int16Array(buffer); + case TYPE_UINT16ARRAY: + return new Uint16Array(buffer); + case TYPE_INT32ARRAY: + return new Int32Array(buffer); + case TYPE_UINT32ARRAY: + return new Uint32Array(buffer); + case TYPE_FLOAT32ARRAY: + return new Float32Array(buffer); + case TYPE_FLOAT64ARRAY: + return new Float64Array(buffer); + default: + throw new Error('Unkown type: ' + type); + } +} + +var localforageSerializer = { + serialize: serialize, + deserialize: deserialize, + stringToBuffer: stringToBuffer, + bufferToString: bufferToString +}; + +/* + * Includes code from: + * + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ +// Open the WebSQL database (automatically creates one if one didn't +// previously exist), using any options set in the config. +function _initStorage$1(options) { + var self = this; + var dbInfo = { + db: null + }; + + if (options) { + for (var i in options) { + dbInfo[i] = typeof options[i] !== 'string' ? options[i].toString() : options[i]; + } + } + + var dbInfoPromise = new Promise$1(function (resolve, reject) { + // Open the database; the openDatabase API will automatically + // create it for us if it doesn't exist. + try { + dbInfo.db = openDatabase(dbInfo.name, String(dbInfo.version), dbInfo.description, dbInfo.size); + } catch (e) { + return reject(e); + } + + // Create our key/value table if it doesn't exist. + dbInfo.db.transaction(function (t) { + t.executeSql('CREATE TABLE IF NOT EXISTS ' + dbInfo.storeName + ' (id INTEGER PRIMARY KEY, key unique, value)', [], function () { + self._dbInfo = dbInfo; + resolve(); + }, function (t, error) { + reject(error); + }); + }); + }); + + dbInfo.serializer = localforageSerializer; + return dbInfoPromise; +} + +function getItem$1(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + t.executeSql('SELECT * FROM ' + dbInfo.storeName + ' WHERE key = ? LIMIT 1', [key], function (t, results) { + var result = results.rows.length ? results.rows.item(0).value : null; + + // Check to see if this is serialized content we need to + // unpack. + if (result) { + result = dbInfo.serializer.deserialize(result); + } + + resolve(result); + }, function (t, error) { + + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function iterate$1(iterator, callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + + dbInfo.db.transaction(function (t) { + t.executeSql('SELECT * FROM ' + dbInfo.storeName, [], function (t, results) { + var rows = results.rows; + var length = rows.length; + + for (var i = 0; i < length; i++) { + var item = rows.item(i); + var result = item.value; + + // Check to see if this is serialized content + // we need to unpack. + if (result) { + result = dbInfo.serializer.deserialize(result); + } + + result = iterator(result, item.key, i + 1); + + // void(0) prevents problems with redefinition + // of `undefined`. + if (result !== void 0) { + resolve(result); + return; + } + } + + resolve(); + }, function (t, error) { + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function _setItem(key, value, callback, retriesLeft) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + // The localStorage API doesn't return undefined values in an + // "expected" way, so undefined is always cast to null in all + // drivers. See: https://github.com/mozilla/localForage/pull/42 + if (value === undefined) { + value = null; + } + + // Save the original value to pass to the callback. + var originalValue = value; + + var dbInfo = self._dbInfo; + dbInfo.serializer.serialize(value, function (value, error) { + if (error) { + reject(error); + } else { + dbInfo.db.transaction(function (t) { + t.executeSql('INSERT OR REPLACE INTO ' + dbInfo.storeName + ' (key, value) VALUES (?, ?)', [key, value], function () { + resolve(originalValue); + }, function (t, error) { + reject(error); + }); + }, function (sqlError) { + // The transaction failed; check + // to see if it's a quota error. + if (sqlError.code === sqlError.QUOTA_ERR) { + // We reject the callback outright for now, but + // it's worth trying to re-run the transaction. + // Even if the user accepts the prompt to use + // more storage on Safari, this error will + // be called. + // + // Try to re-run the transaction. + if (retriesLeft > 0) { + resolve(_setItem.apply(self, [key, originalValue, callback, retriesLeft - 1])); + return; + } + reject(sqlError); + } + }); + } + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function setItem$1(key, value, callback) { + return _setItem.apply(this, [key, value, callback, 1]); +} + +function removeItem$1(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + t.executeSql('DELETE FROM ' + dbInfo.storeName + ' WHERE key = ?', [key], function () { + resolve(); + }, function (t, error) { + + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +// Deletes every item in the table. +// TODO: Find out if this resets the AUTO_INCREMENT number. +function clear$1(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + t.executeSql('DELETE FROM ' + dbInfo.storeName, [], function () { + resolve(); + }, function (t, error) { + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +// Does a simple `COUNT(key)` to get the number of items stored in +// localForage. +function length$1(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + // Ahhh, SQL makes this one soooooo easy. + t.executeSql('SELECT COUNT(key) as c FROM ' + dbInfo.storeName, [], function (t, results) { + var result = results.rows.item(0).c; + + resolve(result); + }, function (t, error) { + + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +// Return the key located at key index X; essentially gets the key from a +// `WHERE id = ?`. This is the most efficient way I can think to implement +// this rarely-used (in my experience) part of the API, but it can seem +// inconsistent, because we do `INSERT OR REPLACE INTO` on `setItem()`, so +// the ID of each key will change every time it's updated. Perhaps a stored +// procedure for the `setItem()` SQL would solve this problem? +// TODO: Don't change ID on `setItem()`. +function key$1(n, callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + t.executeSql('SELECT key FROM ' + dbInfo.storeName + ' WHERE id = ? LIMIT 1', [n + 1], function (t, results) { + var result = results.rows.length ? results.rows.item(0).key : null; + resolve(result); + }, function (t, error) { + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +function keys$1(callback) { + var self = this; + + var promise = new Promise$1(function (resolve, reject) { + self.ready().then(function () { + var dbInfo = self._dbInfo; + dbInfo.db.transaction(function (t) { + t.executeSql('SELECT key FROM ' + dbInfo.storeName, [], function (t, results) { + var keys = []; + + for (var i = 0; i < results.rows.length; i++) { + keys.push(results.rows.item(i).key); + } + + resolve(keys); + }, function (t, error) { + + reject(error); + }); + }); + })["catch"](reject); + }); + + executeCallback(promise, callback); + return promise; +} + +var webSQLStorage = { + _driver: 'webSQLStorage', + _initStorage: _initStorage$1, + iterate: iterate$1, + getItem: getItem$1, + setItem: setItem$1, + removeItem: removeItem$1, + clear: clear$1, + length: length$1, + key: key$1, + keys: keys$1 +}; + +// Config the localStorage backend, using options set in the config. +function _initStorage$2(options) { + var self = this; + var dbInfo = {}; + if (options) { + for (var i in options) { + dbInfo[i] = options[i]; + } + } + + dbInfo.keyPrefix = dbInfo.name + '/'; + + if (dbInfo.storeName !== self._defaultConfig.storeName) { + dbInfo.keyPrefix += dbInfo.storeName + '/'; + } + + self._dbInfo = dbInfo; + dbInfo.serializer = localforageSerializer; + + return Promise$1.resolve(); +} + +// Remove all keys from the datastore, effectively destroying all data in +// the app's key/value store! +function clear$2(callback) { + var self = this; + var promise = self.ready().then(function () { + var keyPrefix = self._dbInfo.keyPrefix; + + for (var i = localStorage.length - 1; i >= 0; i--) { + var key = localStorage.key(i); + + if (key.indexOf(keyPrefix) === 0) { + localStorage.removeItem(key); + } + } + }); + + executeCallback(promise, callback); + return promise; +} + +// Retrieve an item from the store. Unlike the original async_storage +// library in Gaia, we don't modify return values at all. If a key's value +// is `undefined`, we pass that value to the callback function. +function getItem$2(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = self.ready().then(function () { + var dbInfo = self._dbInfo; + var result = localStorage.getItem(dbInfo.keyPrefix + key); + + // If a result was found, parse it from the serialized + // string into a JS object. If result isn't truthy, the key + // is likely undefined and we'll pass it straight to the + // callback. + if (result) { + result = dbInfo.serializer.deserialize(result); + } + + return result; + }); + + executeCallback(promise, callback); + return promise; +} + +// Iterate over all items in the store. +function iterate$2(iterator, callback) { + var self = this; + + var promise = self.ready().then(function () { + var dbInfo = self._dbInfo; + var keyPrefix = dbInfo.keyPrefix; + var keyPrefixLength = keyPrefix.length; + var length = localStorage.length; + + // We use a dedicated iterator instead of the `i` variable below + // so other keys we fetch in localStorage aren't counted in + // the `iterationNumber` argument passed to the `iterate()` + // callback. + // + // See: github.com/mozilla/localForage/pull/435#discussion_r38061530 + var iterationNumber = 1; + + for (var i = 0; i < length; i++) { + var key = localStorage.key(i); + if (key.indexOf(keyPrefix) !== 0) { + continue; + } + var value = localStorage.getItem(key); + + // If a result was found, parse it from the serialized + // string into a JS object. If result isn't truthy, the + // key is likely undefined and we'll pass it straight + // to the iterator. + if (value) { + value = dbInfo.serializer.deserialize(value); + } + + value = iterator(value, key.substring(keyPrefixLength), iterationNumber++); + + if (value !== void 0) { + return value; + } + } + }); + + executeCallback(promise, callback); + return promise; +} + +// Same as localStorage's key() method, except takes a callback. +function key$2(n, callback) { + var self = this; + var promise = self.ready().then(function () { + var dbInfo = self._dbInfo; + var result; + try { + result = localStorage.key(n); + } catch (error) { + result = null; + } + + // Remove the prefix from the key, if a key is found. + if (result) { + result = result.substring(dbInfo.keyPrefix.length); + } + + return result; + }); + + executeCallback(promise, callback); + return promise; +} + +function keys$2(callback) { + var self = this; + var promise = self.ready().then(function () { + var dbInfo = self._dbInfo; + var length = localStorage.length; + var keys = []; + + for (var i = 0; i < length; i++) { + if (localStorage.key(i).indexOf(dbInfo.keyPrefix) === 0) { + keys.push(localStorage.key(i).substring(dbInfo.keyPrefix.length)); + } + } + + return keys; + }); + + executeCallback(promise, callback); + return promise; +} + +// Supply the number of keys in the datastore to the callback function. +function length$2(callback) { + var self = this; + var promise = self.keys().then(function (keys) { + return keys.length; + }); + + executeCallback(promise, callback); + return promise; +} + +// Remove an item from the store, nice and simple. +function removeItem$2(key, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = self.ready().then(function () { + var dbInfo = self._dbInfo; + localStorage.removeItem(dbInfo.keyPrefix + key); + }); + + executeCallback(promise, callback); + return promise; +} + +// Set a key's value and run an optional callback once the value is set. +// Unlike Gaia's implementation, the callback function is passed the value, +// in case you want to operate on that value only after you're sure it +// saved, or something like that. +function setItem$2(key, value, callback) { + var self = this; + + // Cast the key to a string, as that's all we can set as a key. + if (typeof key !== 'string') { + console.warn(key + ' used as a key, but it is not a string.'); + key = String(key); + } + + var promise = self.ready().then(function () { + // Convert undefined values to null. + // https://github.com/mozilla/localForage/pull/42 + if (value === undefined) { + value = null; + } + + // Save the original value to pass to the callback. + var originalValue = value; + + return new Promise$1(function (resolve, reject) { + var dbInfo = self._dbInfo; + dbInfo.serializer.serialize(value, function (value, error) { + if (error) { + reject(error); + } else { + try { + localStorage.setItem(dbInfo.keyPrefix + key, value); + resolve(originalValue); + } catch (e) { + // localStorage capacity exceeded. + // TODO: Make this a specific error/event. + if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { + reject(e); + } + reject(e); + } + } + }); + }); + }); + + executeCallback(promise, callback); + return promise; +} + +var localStorageWrapper = { + _driver: 'localStorageWrapper', + _initStorage: _initStorage$2, + // Default API, from Gaia/localStorage. + iterate: iterate$2, + getItem: getItem$2, + setItem: setItem$2, + removeItem: removeItem$2, + clear: clear$2, + length: length$2, + key: key$2, + keys: keys$2 +}; + +// Custom drivers are stored here when `defineDriver()` is called. +// They are shared across all instances of localForage. +var CustomDrivers = {}; + +var DriverType = { + INDEXEDDB: 'asyncStorage', + LOCALSTORAGE: 'localStorageWrapper', + WEBSQL: 'webSQLStorage' +}; + +var DefaultDriverOrder = [DriverType.INDEXEDDB, DriverType.WEBSQL, DriverType.LOCALSTORAGE]; + +var LibraryMethods = ['clear', 'getItem', 'iterate', 'key', 'keys', 'length', 'removeItem', 'setItem']; + +var DefaultConfig = { + description: '', + driver: DefaultDriverOrder.slice(), + name: 'localforage', + // Default DB size is _JUST UNDER_ 5MB, as it's the highest size + // we can use without a prompt. + size: 4980736, + storeName: 'keyvaluepairs', + version: 1.0 +}; + +var driverSupport = {}; +// Check to see if IndexedDB is available and if it is the latest +// implementation; it's our preferred backend library. We use "_spec_test" +// as the name of the database because it's not the one we'll operate on, +// but it's useful to make sure its using the right spec. +// See: https://github.com/mozilla/localForage/issues/128 +driverSupport[DriverType.INDEXEDDB] = isIndexedDBValid(); + +driverSupport[DriverType.WEBSQL] = isWebSQLValid(); + +driverSupport[DriverType.LOCALSTORAGE] = isLocalStorageValid(); + +var isArray = Array.isArray || function (arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; +}; + +function callWhenReady(localForageInstance, libraryMethod) { + localForageInstance[libraryMethod] = function () { + var _args = arguments; + return localForageInstance.ready().then(function () { + return localForageInstance[libraryMethod].apply(localForageInstance, _args); + }); + }; +} + +function extend() { + for (var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + + if (arg) { + for (var key in arg) { + if (arg.hasOwnProperty(key)) { + if (isArray(arg[key])) { + arguments[0][key] = arg[key].slice(); + } else { + arguments[0][key] = arg[key]; + } + } + } + } + } + + return arguments[0]; +} + +function isLibraryDriver(driverName) { + for (var driver in DriverType) { + if (DriverType.hasOwnProperty(driver) && DriverType[driver] === driverName) { + return true; + } + } + + return false; +} + +var LocalForage = function () { + function LocalForage(options) { + _classCallCheck(this, LocalForage); + + this.INDEXEDDB = DriverType.INDEXEDDB; + this.LOCALSTORAGE = DriverType.LOCALSTORAGE; + this.WEBSQL = DriverType.WEBSQL; + + this._defaultConfig = extend({}, DefaultConfig); + this._config = extend({}, this._defaultConfig, options); + this._driverSet = null; + this._initDriver = null; + this._ready = false; + this._dbInfo = null; + + this._wrapLibraryMethodsWithReady(); + this.setDriver(this._config.driver)["catch"](function () {}); + } + + // Set any config values for localForage; can be called anytime before + // the first API call (e.g. `getItem`, `setItem`). + // We loop through options so we don't overwrite existing config + // values. + + + LocalForage.prototype.config = function config(options) { + // If the options argument is an object, we use it to set values. + // Otherwise, we return either a specified config value or all + // config values. + if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { + // If localforage is ready and fully initialized, we can't set + // any new configuration values. Instead, we return an error. + if (this._ready) { + return new Error("Can't call config() after localforage " + 'has been used.'); + } + + for (var i in options) { + if (i === 'storeName') { + options[i] = options[i].replace(/\W/g, '_'); + } + + if (i === 'version' && typeof options[i] !== 'number') { + return new Error('Database version must be a number.'); + } + + this._config[i] = options[i]; + } + + // after all config options are set and + // the driver option is used, try setting it + if ('driver' in options && options.driver) { + return this.setDriver(this._config.driver); + } + + return true; + } else if (typeof options === 'string') { + return this._config[options]; + } else { + return this._config; + } + }; + + // Used to define a custom driver, shared across all instances of + // localForage. + + + LocalForage.prototype.defineDriver = function defineDriver(driverObject, callback, errorCallback) { + var promise = new Promise$1(function (resolve, reject) { + try { + var driverName = driverObject._driver; + var complianceError = new Error('Custom driver not compliant; see ' + 'https://mozilla.github.io/localForage/#definedriver'); + var namingError = new Error('Custom driver name already in use: ' + driverObject._driver); + + // A driver name should be defined and not overlap with the + // library-defined, default drivers. + if (!driverObject._driver) { + reject(complianceError); + return; + } + if (isLibraryDriver(driverObject._driver)) { + reject(namingError); + return; + } + + var customDriverMethods = LibraryMethods.concat('_initStorage'); + for (var i = 0; i < customDriverMethods.length; i++) { + var customDriverMethod = customDriverMethods[i]; + if (!customDriverMethod || !driverObject[customDriverMethod] || typeof driverObject[customDriverMethod] !== 'function') { + reject(complianceError); + return; + } + } + + var supportPromise = Promise$1.resolve(true); + if ('_support' in driverObject) { + if (driverObject._support && typeof driverObject._support === 'function') { + supportPromise = driverObject._support(); + } else { + supportPromise = Promise$1.resolve(!!driverObject._support); + } + } + + supportPromise.then(function (supportResult) { + driverSupport[driverName] = supportResult; + CustomDrivers[driverName] = driverObject; + resolve(); + }, reject); + } catch (e) { + reject(e); + } + }); + + executeTwoCallbacks(promise, callback, errorCallback); + return promise; + }; + + LocalForage.prototype.driver = function driver() { + return this._driver || null; + }; + + LocalForage.prototype.getDriver = function getDriver(driverName, callback, errorCallback) { + var self = this; + var getDriverPromise = Promise$1.resolve().then(function () { + if (isLibraryDriver(driverName)) { + switch (driverName) { + case self.INDEXEDDB: + return asyncStorage; + case self.LOCALSTORAGE: + return localStorageWrapper; + case self.WEBSQL: + return webSQLStorage; + } + } else if (CustomDrivers[driverName]) { + return CustomDrivers[driverName]; + } else { + throw new Error('Driver not found.'); + } + }); + executeTwoCallbacks(getDriverPromise, callback, errorCallback); + return getDriverPromise; + }; + + LocalForage.prototype.getSerializer = function getSerializer(callback) { + var serializerPromise = Promise$1.resolve(localforageSerializer); + executeTwoCallbacks(serializerPromise, callback); + return serializerPromise; + }; + + LocalForage.prototype.ready = function ready(callback) { + var self = this; + + var promise = self._driverSet.then(function () { + if (self._ready === null) { + self._ready = self._initDriver(); + } + + return self._ready; + }); + + executeTwoCallbacks(promise, callback, callback); + return promise; + }; + + LocalForage.prototype.setDriver = function setDriver(drivers, callback, errorCallback) { + var self = this; + + if (!isArray(drivers)) { + drivers = [drivers]; } - update_labels(globals.LABELS); - } - }); - reader.readAsDataURL(file); - } -} + var supportedDrivers = this._getSupportedDrivers(drivers); -function update_labels(labels) { - labels.map( (label) => { - label.title = `${globals.BUILDING}${globals.FLOOR}-${label.id}`; - }) -} -function export_image(e) { - const export_canvas = document.createElement('canvas'); - export_canvas.height = 3000; - export_canvas.width = Math.round(export_canvas.height * Math.sqrt(2)); - const ctx = export_canvas.getContext('2d'); - ctx.clearRect(0, 0, export_canvas.height, export_canvas.width); + function setDriverToConfig() { + self._config.driver = self.driver(); + } - // Rescale image such that the largest dimension of the image fits nicely - const working_height = export_canvas.height - 200; - const working_width = export_canvas.width - 200; - const max_x = globals.CVS.image.width; - const max_y = globals.CVS.image.height; - let ratio = 1; + function extendSelfWithDriver(driver) { + self._extend(driver); + setDriverToConfig(); - let xratio = working_width / max_x; - let yratio = working_height / max_y; - ratio = Math.min(xratio, yratio); + self._ready = self._initStorage(self._config); + return self._ready; + } - const x_offset = (export_canvas.width - max_x * ratio) / 2; - const y_offset = (export_canvas.height - max_y * ratio) / 2; + function initDriver(supportedDrivers) { + return function () { + var currentDriverIndex = 0; - // make a deep copy of LABELS so as not to mutate it - let temp_labels = JSON.parse(JSON.stringify(globals.LABELS)); - temp_labels.map( (label) => { - label.x = label.x * ratio + x_offset; - label.y = label.y * ratio + y_offset; - }); - ctx.drawImage(globals.CVS.image, 0, 0, globals.CVS.image.width, globals.CVS.image.height, - x_offset, y_offset, globals.CVS.image.width * ratio, - globals.CVS.image.height * ratio); + function driverPromiseLoop() { + while (currentDriverIndex < supportedDrivers.length) { + var driverName = supportedDrivers[currentDriverIndex]; + currentDriverIndex++; - // These ratios are how much to scale the labels and overlay to the enlarged - // canvas - let x_ratio = working_height / globals.CVS.image.height; - let y_ratio = working_width / globals.CVS.image.width; - let draw_ratio = Math.min(x_ratio, y_ratio); - draw_ratio = 1; + self._dbInfo = null; + self._ready = null; - temp_labels.map( (label) => { globals.CVS.draw_label(label, ctx, draw_ratio); }); - __WEBPACK_IMPORTED_MODULE_6_vex_js___default.a.dialog.open({ - message: 'Enter building overlay text', - input: "", - callback: (text) => { - if (text === undefined) { - globals.CVS.draw_overlay(export_canvas, ctx); - } - else { - globals.CVS.draw_overlay(export_canvas, ctx, text.letter); - } - let buffer = canvasBuffer(export_canvas, 'image/png'); - electron.remote.getGlobal('data').exportedImage = buffer; - electron.ipcRenderer.send('export_image', buffer); - export_canvas.remove(); // Garbage collection - } - }); -} + return self.getDriver(driverName).then(extendSelfWithDriver)["catch"](driverPromiseLoop); + } + setDriverToConfig(); + var error = new Error('No available storage method found.'); + self._driverSet = Promise$1.reject(error); + return self._driverSet; + } -function export_table() { - var element = document.getElementById('export-table'); - htmlpdf(element, { - margin: 0, - filename: 'export.pdf', - image: { type: 'jpeg', quality: 0.98 }, - html2canvas: { dpi: 192, letterRendering: true }, - jsPDF: { unit: 'in', format: 'A4', orientation: 'portrait' } - }); -} + return driverPromiseLoop(); + }; + } -init(); + // There might be a driver initialization in progress + // so wait for it to finish in order to avoid a possible + // race condition to set _dbInfo + var oldDriverSetDone = this._driverSet !== null ? this._driverSet["catch"](function () { + return Promise$1.resolve(); + }) : Promise$1.resolve(); + + this._driverSet = oldDriverSetDone.then(function () { + var driverName = supportedDrivers[0]; + self._dbInfo = null; + self._ready = null; + + return self.getDriver(driverName).then(function (driver) { + self._driver = driver._driver; + setDriverToConfig(); + self._wrapLibraryMethodsWithReady(); + self._initDriver = initDriver(supportedDrivers); + }); + })["catch"](function () { + setDriverToConfig(); + var error = new Error('No available storage method found.'); + self._driverSet = Promise$1.reject(error); + return self._driverSet; + }); + + executeTwoCallbacks(this._driverSet, callback, errorCallback); + return this._driverSet; + }; + + LocalForage.prototype.supports = function supports(driverName) { + return !!driverSupport[driverName]; + }; + LocalForage.prototype._extend = function _extend(libraryMethodsAndProperties) { + extend(this, libraryMethodsAndProperties); + }; + + LocalForage.prototype._getSupportedDrivers = function _getSupportedDrivers(drivers) { + var supportedDrivers = []; + for (var i = 0, len = drivers.length; i < len; i++) { + var driverName = drivers[i]; + if (this.supports(driverName)) { + supportedDrivers.push(driverName); + } + } + return supportedDrivers; + }; + + LocalForage.prototype._wrapLibraryMethodsWithReady = function _wrapLibraryMethodsWithReady() { + // Add a stub for each driver API method that delays the call to the + // corresponding driver method until localForage is ready. These stubs + // will be replaced by the driver methods as soon as the driver is + // loaded, so there is no performance impact. + for (var i = 0; i < LibraryMethods.length; i++) { + callWhenReady(this, LibraryMethods[i]); + } + }; + + LocalForage.prototype.createInstance = function createInstance(options) { + return new LocalForage(options); + }; + + return LocalForage; +}(); + +// The actual localForage object that we expose as a module or via a +// global. It's extended by pulling in one of our other libraries. + + +var localforage_js = new LocalForage(); + +module.exports = localforage_js; + +},{"3":3}]},{},[4])(4) +}); /***/ }), -/* 40 */ +/* 41 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = __webpack_require__(41); +module.exports = __webpack_require__(42); /***/ }), -/* 41 */ +/* 42 */ /***/ (function(module, exports, __webpack_require__) { var isFactory = __webpack_require__(1).isFactory; -var typedFactory = __webpack_require__(42); +var typedFactory = __webpack_require__(43); var emitter = __webpack_require__(26); -var importFactory = __webpack_require__(45); -var configFactory = __webpack_require__(46); +var importFactory = __webpack_require__(46); +var configFactory = __webpack_require__(47); /** * Math.js core. Creates a new, empty math.js instance @@ -7809,10 +10217,10 @@ exports.create = function create (options) { /***/ }), -/* 42 */ +/* 43 */ /***/ (function(module, exports, __webpack_require__) { -var typedFunction = __webpack_require__(43); +var typedFunction = __webpack_require__(44); var digits = __webpack_require__(2).digits; // returns a new instance of typed-function @@ -8052,7 +10460,7 @@ exports.create = function create(type) { /***/ }), -/* 43 */ +/* 44 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -9449,7 +11857,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 44 */ +/* 45 */ /***/ (function(module, exports) { function E () { @@ -9521,7 +11929,7 @@ module.exports = E; /***/ }), -/* 45 */ +/* 46 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -9835,7 +12243,7 @@ exports.lazy = true; /***/ }), -/* 46 */ +/* 47 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -9966,29 +12374,29 @@ exports.factory = factory; /***/ }), -/* 47 */ +/* 48 */ /***/ (function(module, exports, __webpack_require__) { module.exports = [ // types __webpack_require__(20), __webpack_require__(29), - __webpack_require__(50), __webpack_require__(51), - __webpack_require__(53), - __webpack_require__(55), + __webpack_require__(52), + __webpack_require__(54), __webpack_require__(56), __webpack_require__(57), + __webpack_require__(58), // construction functions - __webpack_require__(58), + __webpack_require__(59), __webpack_require__(0), - __webpack_require__(59) + __webpack_require__(60) ]; /***/ }), -/* 48 */ +/* 49 */ /***/ (function(module, exports) { /** @@ -10177,7 +12585,7 @@ exports.toFixed = function (value, precision) { /***/ }), -/* 49 */ +/* 50 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -10194,7 +12602,7 @@ exports.isBoolean = function(value) { /***/ }), -/* 50 */ +/* 51 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11635,7 +14043,7 @@ exports.lazy = false; // no lazy loading, as we alter type.Matrix._storage /***/ }), -/* 51 */ +/* 52 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11783,7 +14191,7 @@ exports.factory = factory; /***/ }), -/* 52 */ +/* 53 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11977,7 +14385,7 @@ exports.factory = factory; /***/ }), -/* 53 */ +/* 54 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -11986,7 +14394,7 @@ exports.factory = factory; function factory (type, config, load, typed) { var smaller = load(__webpack_require__(32)); - var larger = load(__webpack_require__(54)); + var larger = load(__webpack_require__(55)); var oneOverLogPhi = 1.0 / Math.log((1.0 + Math.sqrt(5.0)) / 2.0); @@ -12333,7 +14741,7 @@ exports.factory = factory; /***/ }), -/* 54 */ +/* 55 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -12519,7 +14927,7 @@ exports.factory = factory; /***/ }), -/* 55 */ +/* 56 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -12759,7 +15167,7 @@ exports.factory = factory; /***/ }), -/* 56 */ +/* 57 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13049,7 +15457,7 @@ exports.factory = factory; /***/ }), -/* 57 */ +/* 58 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13372,7 +15780,7 @@ exports.factory = factory; /***/ }), -/* 58 */ +/* 59 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13443,7 +15851,7 @@ exports.factory = factory; /***/ }), -/* 59 */ +/* 60 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13511,21 +15919,20 @@ exports.factory = factory; /***/ }), -/* 60 */ +/* 61 */ /***/ (function(module, exports, __webpack_require__) { module.exports = [ - __webpack_require__(61), __webpack_require__(62), + __webpack_require__(63), __webpack_require__(36), - __webpack_require__(65), __webpack_require__(66), - __webpack_require__(37), __webpack_require__(67), + __webpack_require__(37), __webpack_require__(68), __webpack_require__(69), __webpack_require__(70), - __webpack_require__(72), + __webpack_require__(71), __webpack_require__(73), __webpack_require__(74), __webpack_require__(75), @@ -13534,16 +15941,17 @@ module.exports = [ __webpack_require__(78), __webpack_require__(79), __webpack_require__(80), - __webpack_require__(84), + __webpack_require__(81), __webpack_require__(85), __webpack_require__(86), __webpack_require__(87), - __webpack_require__(88) + __webpack_require__(88), + __webpack_require__(89) ]; /***/ }), -/* 61 */ +/* 62 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13694,7 +16102,7 @@ exports.factory = factory; /***/ }), -/* 62 */ +/* 63 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13802,7 +16210,7 @@ exports.factory = factory; /***/ }), -/* 63 */ +/* 64 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13834,7 +16242,7 @@ module.exports = function deepMap(array, callback, skipZeros) { /***/ }), -/* 64 */ +/* 65 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -13950,7 +16358,7 @@ exports.factory = factory; /***/ }), -/* 65 */ +/* 66 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14127,7 +16535,7 @@ exports.factory = factory; /***/ }), -/* 66 */ +/* 67 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14213,7 +16621,7 @@ exports.factory = factory; /***/ }), -/* 67 */ +/* 68 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14324,7 +16732,7 @@ exports.factory = factory; /***/ }), -/* 68 */ +/* 69 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14376,7 +16784,7 @@ exports.factory = factory; /***/ }), -/* 69 */ +/* 70 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14459,7 +16867,7 @@ exports.factory = factory; /***/ }), -/* 70 */ +/* 71 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14469,7 +16877,7 @@ var util = __webpack_require__(5); function factory (type, config, load, typed) { var matrix = load(__webpack_require__(0)); - var divideScalar = load(__webpack_require__(71)); + var divideScalar = load(__webpack_require__(72)); var addScalar = load(__webpack_require__(17)); var multiply = load(__webpack_require__(19)); var unaryMinus = load(__webpack_require__(23)); @@ -14673,7 +17081,7 @@ exports.factory = factory; /***/ }), -/* 71 */ +/* 72 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14739,7 +17147,7 @@ exports.factory = factory; /***/ }), -/* 72 */ +/* 73 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14836,7 +17244,7 @@ exports.factory = factory; /***/ }), -/* 73 */ +/* 74 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -14922,7 +17330,7 @@ exports.factory = factory; /***/ }), -/* 74 */ +/* 75 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15063,7 +17471,7 @@ exports.factory = factory; /***/ }), -/* 75 */ +/* 76 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15206,7 +17614,7 @@ exports.factory = factory; /***/ }), -/* 76 */ +/* 77 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15492,7 +17900,7 @@ exports.factory = factory; /***/ }), -/* 77 */ +/* 78 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15573,7 +17981,7 @@ exports.factory = factory; /***/ }), -/* 78 */ +/* 79 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15718,7 +18126,7 @@ exports.factory = factory; /***/ }), -/* 79 */ +/* 80 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15780,7 +18188,7 @@ exports.factory = factory; /***/ }), -/* 80 */ +/* 81 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -15794,7 +18202,7 @@ function factory (type, config, load, typed) { var compareDesc = function (a, b) { return -compareAsc(a, b); }; - var compareNatural = load(__webpack_require__(81)); + var compareNatural = load(__webpack_require__(82)); /** * Sort the items in a matrix. @@ -15911,16 +18319,16 @@ exports.factory = factory; /***/ }), -/* 81 */ +/* 82 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var naturalSort = __webpack_require__(82); +var naturalSort = __webpack_require__(83); function factory (type, config, load, typed) { - var getTypeOf = load(__webpack_require__(83)); + var getTypeOf = load(__webpack_require__(84)); var matrix = load(__webpack_require__(0)); var compare = load(__webpack_require__(25)); @@ -16184,7 +18592,7 @@ exports.factory = factory; /***/ }), -/* 82 */ +/* 83 */ /***/ (function(module, exports) { /* @@ -16235,7 +18643,7 @@ module.exports = function naturalSort (a, b) { /***/ }), -/* 83 */ +/* 84 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -16319,7 +18727,7 @@ exports.factory = factory; /***/ }), -/* 84 */ +/* 85 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -16387,7 +18795,7 @@ exports.factory = factory; /***/ }), -/* 85 */ +/* 86 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -16627,7 +19035,7 @@ exports.factory = factory; /***/ }), -/* 86 */ +/* 87 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -16781,7 +19189,7 @@ exports.factory = factory; /***/ }), -/* 87 */ +/* 88 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -16966,7 +19374,7 @@ exports.factory = factory; /***/ }), -/* 88 */ +/* 89 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -17107,13 +19515,13 @@ exports.factory = factory; /***/ }), -/* 89 */ +/* 90 */ /***/ (function(module, exports, __webpack_require__) { // style-loader: Adds some css to the DOM by adding a