[PATCH] Persist opened nodes in tree - Mailing list pgadmin-hackers
From | Versus Void |
---|---|
Subject | [PATCH] Persist opened nodes in tree |
Date | |
Msg-id | 20170718084200.6634-2-chaoskeeper@mail.ru Whole thread Raw |
In response to | [PATCH] Persist opened nodes in tree (Versus Void <chaoskeeper@mail.ru>) |
Responses |
Re: [PATCH] Persist opened nodes in tree
|
List | pgadmin-hackers |
---web/pgadmin/browser/__init__.py | 13 +-.../browser/static/vendor/jStorage/jstorage.js | 996 +++++++++++++++++++++.../browser/static/vendor/jStorage/jstorage.min.js| 16 +.../browser/templates/browser/js/browser.js | 6 +-4 files changed, 1029 insertions(+), 2 deletions(-)create mode100644 web/pgadmin/browser/static/vendor/jStorage/jstorage.jscreate mode 100644 web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 77e052f0..ecf3afba 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -100,13 +100,24 @@ class BrowserModule(PgAdminModule): 'preloaded': True }) scripts.append({ + 'name': 'jStorage', + 'path': url_for( + 'browser.static', + filename='vendor/jStorage/jstorage' if + current_app.debug else 'vendor/jStorage/jstorage.min' + ), + 'deps': ['jquery'], + 'exports': 'jStorage', + 'preloaded': True + }) + scripts.append({ 'name': 'jquery.acitree', 'path': url_for( 'browser.static', filename='vendor/aciTree/jquery.aciTree' if current_app.debug else 'vendor/aciTree/jquery.aciTree.min' ), - 'deps': ['jquery', 'jquery.aciplugin'], + 'deps': ['jquery', 'jquery.aciplugin', 'jStorage'], 'exports': 'aciPluginClass.plugins.aciTree', 'preloaded': True }) diff --git a/web/pgadmin/browser/static/vendor/jStorage/jstorage.js b/web/pgadmin/browser/static/vendor/jStorage/jstorage.js new file mode 100644 index 00000000..1ac8fccc --- /dev/null +++ b/web/pgadmin/browser/static/vendor/jStorage/jstorage.js @@ -0,0 +1,996 @@ +/* + * ----------------------------- JSTORAGE ------------------------------------- + * Simple local storage wrapper to save data on the browser side, supporting + * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ + * + * Author: Andris Reinman, andris.reinman@gmail.com + * Project homepage: www.jstorage.info + * + * Licensed under Unlicense: + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org/> + */ + +/* global ActiveXObject: false */ +/* jshint browser: true */ + +(function() { + 'use strict'; + + var + /* jStorage version */ + JSTORAGE_VERSION = '0.4.12', + + /* detect a dollar object or create one if not found */ + $ = window.jQuery || window.$ || (window.$ = {}), + + /* check for a JSON handling support */ + JSON = { + parse: window.JSON && (window.JSON.parse || window.JSON.decode) || + String.prototype.evalJSON && function(str) { + return String(str).evalJSON(); + } || + $.parseJSON || + $.evalJSON, + stringify: Object.toJSON || + window.JSON && (window.JSON.stringify || window.JSON.encode) || + $.toJSON + }; + + // Break if no JSON support was found + if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') { + throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page'); + } + + var + /* This is the object, that holds the cached values */ + _storage = { + __jstorage_meta: { + CRC32: {} + } + }, + + /* Actual browser storage (localStorage or globalStorage['domain']) */ + _storage_service = { + jStorage: '{}' + }, + + /* DOM element for older IE versions, holds userData behavior */ + _storage_elm = null, + + /* How much space does the storage take */ + _storage_size = 0, + + /* which backend is currently used */ + _backend = false, + + /* onchange observers */ + _observers = {}, + + /* timeout to wait after onchange event */ + _observer_timeout = false, + + /* last update time */ + _observer_update = 0, + + /* pubsub observers */ + _pubsub_observers = {}, + + /* skip published items older than current timestamp */ + _pubsub_last = +new Date(), + + /* Next check for TTL */ + _ttl_timeout, + + /** + * XML encoding and decoding as XML nodes can't be JSON'ized + * XML nodes are encoded and decoded if the node is the value to be saved + * but not if it's as a property of another object + * Eg. - + * $.jStorage.set('key', xmlNode); // IS OK + * $.jStorage.set('key', {xml: xmlNode}); // NOT OK + */ + _XMLService = { + + /** + * Validates a XML node to be XML + * based on jQuery.isXML function + */ + isXML: function(elm) { + var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; + return documentElement ? documentElement.nodeName !== 'HTML' : false; + }, + + /** + * Encodes a XML node to string + * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ + */ + encode: function(xmlNode) { + if (!this.isXML(xmlNode)) { + return false; + } + try { // Mozilla, Webkit, Opera + return new XMLSerializer().serializeToString(xmlNode); + } catch (E1) { + try { // IE + return xmlNode.xml; + } catch (E2) {} + } + return false; + }, + + /** + * Decodes a XML node from string + * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ + */ + decode: function(xmlString) { + var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) || + (window.ActiveXObject && function(_xmlString) { + var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); + xml_doc.async = 'false'; + xml_doc.loadXML(_xmlString); + return xml_doc; + }), + resultXML; + if (!dom_parser) { + return false; + } + resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml'); + return this.isXML(resultXML) ? resultXML : false; + } + }; + + + ////////////////////////// PRIVATE METHODS //////////////////////// + + /** + * Initialization function. Detects if the browser supports DOM Storage + * or userData behavior and behaves accordingly. + */ + function _init() { + /* Check if browser supports localStorage */ + var localStorageReallyWorks = false; + if ('localStorage' in window) { + try { + window.localStorage.setItem('_tmptest', 'tmpval'); + localStorageReallyWorks = true; + window.localStorage.removeItem('_tmptest'); + } catch (BogusQuotaExceededErrorOnIos5) { + // Thanks be to iOS5 Private Browsing mode which throws + // QUOTA_EXCEEDED_ERRROR DOM Exception 22. + } + } + + if (localStorageReallyWorks) { + try { + if (window.localStorage) { + _storage_service = window.localStorage; + _backend = 'localStorage'; + _observer_update = _storage_service.jStorage_update; + } + } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ } + } + /* Check if browser supports globalStorage */ + else if ('globalStorage' in window) { + try { + if (window.globalStorage) { + if (window.location.hostname == 'localhost') { + _storage_service = window.globalStorage['localhost.localdomain']; + } else { + _storage_service = window.globalStorage[window.location.hostname]; + } + _backend = 'globalStorage'; + _observer_update = _storage_service.jStorage_update; + } + } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ } + } + /* Check if browser supports userData behavior */ + else { + _storage_elm = document.createElement('link'); + if (_storage_elm.addBehavior) { + + /* Use a DOM element to act as userData storage */ + _storage_elm.style.behavior = 'url(#default#userData)'; + + /* userData element needs to be inserted into the DOM! */ + document.getElementsByTagName('head')[0].appendChild(_storage_elm); + + try { + _storage_elm.load('jStorage'); + } catch (E) { + // try to reset cache + _storage_elm.setAttribute('jStorage', '{}'); + _storage_elm.save('jStorage'); + _storage_elm.load('jStorage'); + } + + var data = '{}'; + try { + data = _storage_elm.getAttribute('jStorage'); + } catch (E5) {} + + try { + _observer_update = _storage_elm.getAttribute('jStorage_update'); + } catch (E6) {} + + _storage_service.jStorage = data; + _backend = 'userDataBehavior'; + } else { + _storage_elm = null; + return; + } + } + + // Load data from storage + _load_storage(); + + // remove dead keys + _handleTTL(); + + // start listening for changes + _setupObserver(); + + // initialize publish-subscribe service + _handlePubSub(); + + // handle cached navigation + if ('addEventListener' in window) { + window.addEventListener('pageshow', function(event) { + if (event.persisted) { + _storageObserver(); + } + }, false); + } + } + + /** + * Reload data from storage when needed + */ + function _reloadData() { + var data = '{}'; + + if (_backend == 'userDataBehavior') { + _storage_elm.load('jStorage'); + + try { + data = _storage_elm.getAttribute('jStorage'); + } catch (E5) {} + + try { + _observer_update = _storage_elm.getAttribute('jStorage_update'); + } catch (E6) {} + + _storage_service.jStorage = data; + } + + _load_storage(); + + // remove dead keys + _handleTTL(); + + _handlePubSub(); + } + + /** + * Sets up a storage change observer + */ + function _setupObserver() { + if (_backend == 'localStorage' || _backend == 'globalStorage') { + if ('addEventListener' in window) { + window.addEventListener('storage', _storageObserver, false); + } else { + document.attachEvent('onstorage', _storageObserver); + } + } else if (_backend == 'userDataBehavior') { + setInterval(_storageObserver, 1000); + } + } + + /** + * Fired on any kind of data change, needs to check if anything has + * really been changed + */ + function _storageObserver() { + var updateTime; + // cumulate change notifications with timeout + clearTimeout(_observer_timeout); + _observer_timeout = setTimeout(function() { + + if (_backend == 'localStorage' || _backend == 'globalStorage') { + updateTime = _storage_service.jStorage_update; + } else if (_backend == 'userDataBehavior') { + _storage_elm.load('jStorage'); + try { + updateTime = _storage_elm.getAttribute('jStorage_update'); + } catch (E5) {} + } + + if (updateTime && updateTime != _observer_update) { + _observer_update = updateTime; + _checkUpdatedKeys(); + } + + }, 25); + } + + /** + * Reloads the data and checks if any keys are changed + */ + function _checkUpdatedKeys() { + var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)), + newCrc32List; + + _reloadData(); + newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)); + + var key, + updated = [], + removed = []; + + for (key in oldCrc32List) { + if (oldCrc32List.hasOwnProperty(key)) { + if (!newCrc32List[key]) { + removed.push(key); + continue; + } + if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') { + updated.push(key); + } + } + } + + for (key in newCrc32List) { + if (newCrc32List.hasOwnProperty(key)) { + if (!oldCrc32List[key]) { + updated.push(key); + } + } + } + + _fireObservers(updated, 'updated'); + _fireObservers(removed, 'deleted'); + } + + /** + * Fires observers for updated keys + * + * @param {Array|String} keys Array of key names or a key + * @param {String} action What happened with the value (updated, deleted, flushed) + */ + function _fireObservers(keys, action) { + keys = [].concat(keys || []); + + var i, j, len, jlen; + + if (action == 'flushed') { + keys = []; + for (var key in _observers) { + if (_observers.hasOwnProperty(key)) { + keys.push(key); + } + } + action = 'deleted'; + } + for (i = 0, len = keys.length; i < len; i++) { + if (_observers[keys[i]]) { + for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) { + _observers[keys[i]][j](keys[i], action); + } + } + if (_observers['*']) { + for (j = 0, jlen = _observers['*'].length; j < jlen; j++) { + _observers['*'][j](keys[i], action); + } + } + } + } + + /** + * Publishes key change to listeners + */ + function _publishChange() { + var updateTime = (+new Date()).toString(); + + if (_backend == 'localStorage' || _backend == 'globalStorage') { + try { + _storage_service.jStorage_update = updateTime; + } catch (E8) { + // safari private mode has been enabled after the jStorage initialization + _backend = false; + } + } else if (_backend == 'userDataBehavior') { + _storage_elm.setAttribute('jStorage_update', updateTime); + _storage_elm.save('jStorage'); + } + + _storageObserver(); + } + + /** + * Loads the data from the storage based on the supported mechanism + */ + function _load_storage() { + /* if jStorage string is retrieved, then decode it */ + if (_storage_service.jStorage) { + try { + _storage = JSON.parse(String(_storage_service.jStorage)); + } catch (E6) { + _storage_service.jStorage = '{}'; + } + } else { + _storage_service.jStorage = '{}'; + } + _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; + + if (!_storage.__jstorage_meta) { + _storage.__jstorage_meta = {}; + } + if (!_storage.__jstorage_meta.CRC32) { + _storage.__jstorage_meta.CRC32 = {}; + } + } + + /** + * This functions provides the 'save' mechanism to store the jStorage object + */ + function _save() { + _dropOldEvents(); // remove expired events + try { + _storage_service.jStorage = JSON.stringify(_storage); + // If userData is used as the storage engine, additional + if (_storage_elm) { + _storage_elm.setAttribute('jStorage', _storage_service.jStorage); + _storage_elm.save('jStorage'); + } + _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; + } catch (E7) { /* probably cache is full, nothing is saved this way*/ } + } + + /** + * Function checks if a key is set and is string or numberic + * + * @param {String} key Key name + */ + function _checkKey(key) { + if (typeof key != 'string' && typeof key != 'number') { + throw new TypeError('Key name must be string or numeric'); + } + if (key == '__jstorage_meta') { + throw new TypeError('Reserved key name'); + } + return true; + } + + /** + * Removes expired keys + */ + function _handleTTL() { + var curtime, i, TTL, CRC32, nextExpire = Infinity, + changed = false, + deleted = []; + + clearTimeout(_ttl_timeout); + + if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') { + // nothing to do here + return; + } + + curtime = +new Date(); + TTL = _storage.__jstorage_meta.TTL; + + CRC32 = _storage.__jstorage_meta.CRC32; + for (i in TTL) { + if (TTL.hasOwnProperty(i)) { + if (TTL[i] <= curtime) { + delete TTL[i]; + delete CRC32[i]; + delete _storage[i]; + changed = true; + deleted.push(i); + } else if (TTL[i] < nextExpire) { + nextExpire = TTL[i]; + } + } + } + + // set next check + if (nextExpire != Infinity) { + _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF)); + } + + // save changes + if (changed) { + _save(); + _publishChange(); + _fireObservers(deleted, 'deleted'); + } + } + + /** + * Checks if there's any events on hold to be fired to listeners + */ + function _handlePubSub() { + var i, len; + if (!_storage.__jstorage_meta.PubSub) { + return; + } + var pubelm, + _pubsubCurrent = _pubsub_last, + needFired = []; + + for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) { + pubelm = _storage.__jstorage_meta.PubSub[i]; + if (pubelm[0] > _pubsub_last) { + _pubsubCurrent = pubelm[0]; + needFired.unshift(pubelm); + } + } + + for (i = needFired.length - 1; i >= 0; i--) { + _fireSubscribers(needFired[i][1], needFired[i][2]); + } + + _pubsub_last = _pubsubCurrent; + } + + /** + * Fires all subscriber listeners for a pubsub channel + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload data to deliver + */ + function _fireSubscribers(channel, payload) { + if (_pubsub_observers[channel]) { + for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) { + // send immutable data that can't be modified by listeners + try { + _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload))); + } catch (E) {} + } + } + } + + /** + * Remove old events from the publish stream (at least 2sec old) + */ + function _dropOldEvents() { + if (!_storage.__jstorage_meta.PubSub) { + return; + } + + var retire = +new Date() - 2000; + + for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) { + if (_storage.__jstorage_meta.PubSub[i][0] <= retire) { + // deleteCount is needed for IE6 + _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i); + break; + } + } + + if (!_storage.__jstorage_meta.PubSub.length) { + delete _storage.__jstorage_meta.PubSub; + } + + } + + /** + * Publish payload to a channel + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to send to the subscribers + */ + function _publish(channel, payload) { + if (!_storage.__jstorage_meta) { + _storage.__jstorage_meta = {}; + } + if (!_storage.__jstorage_meta.PubSub) { + _storage.__jstorage_meta.PubSub = []; + } + + _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]); + + _save(); + _publishChange(); + } + + + /** + * JS Implementation of MurmurHash2 + * + * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed) + * + * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a> + * @see http://github.com/garycourt/murmurhash-js + * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a> + * @see http://sites.google.com/site/murmurhash/ + * + * @param {string} str ASCII only + * @param {number} seed Positive integer only + * @return {number} 32-bit positive integer hash + */ + + function murmurhash2_32_gc(str, seed) { + var + l = str.length, + h = seed ^ l, + i = 0, + k; + + while (l >= 4) { + k = + ((str.charCodeAt(i) & 0xff)) | + ((str.charCodeAt(++i) & 0xff) << 8) | + ((str.charCodeAt(++i) & 0xff) << 16) | + ((str.charCodeAt(++i) & 0xff) << 24); + + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + k ^= k >>> 24; + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; + + l -= 4; + ++i; + } + + switch (l) { + case 3: + h ^= (str.charCodeAt(i + 2) & 0xff) << 16; + /* falls through */ + case 2: + h ^= (str.charCodeAt(i + 1) & 0xff) << 8; + /* falls through */ + case 1: + h ^= (str.charCodeAt(i) & 0xff); + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + } + + h ^= h >>> 13; + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + h ^= h >>> 15; + + return h >>> 0; + } + + ////////////////////////// PUBLIC INTERFACE ///////////////////////// + + $.jStorage = { + /* Version number */ + version: JSTORAGE_VERSION, + + /** + * Sets a key's value. + * + * @param {String} key Key to set. If this value is not set or not + * a string an exception is raised. + * @param {Mixed} value Value to set. This can be any value that is JSON + * compatible (Numbers, Strings, Objects etc.). + * @param {Object} [options] - possible options to use + * @param {Number} [options.TTL] - optional TTL value, in milliseconds + * @return {Mixed} the used value + */ + set: function(key, value, options) { + _checkKey(key); + + options = options || {}; + + // undefined values are deleted automatically + if (typeof value == 'undefined') { + this.deleteKey(key); + return value; + } + + if (_XMLService.isXML(value)) { + value = { + _is_xml: true, + xml: _XMLService.encode(value) + }; + } else if (typeof value == 'function') { + return undefined; // functions can't be saved! + } else if (value && typeof value == 'object') { + // clone the object before saving to _storage tree + value = JSON.parse(JSON.stringify(value)); + } + + _storage[key] = value; + + _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); + + this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + + _fireObservers(key, 'updated'); + return value; + }, + + /** + * Looks up a key in cache + * + * @param {String} key - Key to look up. + * @param {mixed} def - Default value to return, if key didn't exist. + * @return {Mixed} the key value, default value or null + */ + get: function(key, def) { + _checkKey(key); + if (key in _storage) { + if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) { + return _XMLService.decode(_storage[key].xml); + } else { + return _storage[key]; + } + } + return typeof(def) == 'undefined' ? null : def; + }, + + /** + * Deletes a key from cache. + * + * @param {String} key - Key to delete. + * @return {Boolean} true if key existed or false if it didn't + */ + deleteKey: function(key) { + _checkKey(key); + if (key in _storage) { + delete _storage[key]; + // remove from TTL list + if (typeof _storage.__jstorage_meta.TTL == 'object' && + key in _storage.__jstorage_meta.TTL) { + delete _storage.__jstorage_meta.TTL[key]; + } + + delete _storage.__jstorage_meta.CRC32[key]; + + _save(); + _publishChange(); + _fireObservers(key, 'deleted'); + return true; + } + return false; + }, + + /** + * Sets a TTL for a key, or remove it if ttl value is 0 or below + * + * @param {String} key - key to set the TTL for + * @param {Number} ttl - TTL timeout in milliseconds + * @return {Boolean} true if key existed or false if it didn't + */ + setTTL: function(key, ttl) { + var curtime = +new Date(); + _checkKey(key); + ttl = Number(ttl) || 0; + if (key in _storage) { + + if (!_storage.__jstorage_meta.TTL) { + _storage.__jstorage_meta.TTL = {}; + } + + // Set TTL value for the key + if (ttl > 0) { + _storage.__jstorage_meta.TTL[key] = curtime + ttl; + } else { + delete _storage.__jstorage_meta.TTL[key]; + } + + _save(); + + _handleTTL(); + + _publishChange(); + return true; + } + return false; + }, + + /** + * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set + * + * @param {String} key Key to check + * @return {Number} Remaining TTL in milliseconds + */ + getTTL: function(key) { + var curtime = +new Date(), + ttl; + _checkKey(key); + if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) { + ttl = _storage.__jstorage_meta.TTL[key] - curtime; + return ttl || 0; + } + return 0; + }, + + /** + * Deletes everything in cache. + * + * @return {Boolean} Always true + */ + flush: function() { + _storage = { + __jstorage_meta: { + CRC32: {} + } + }; + _save(); + _publishChange(); + _fireObservers(null, 'flushed'); + return true; + }, + + /** + * Returns a read-only copy of _storage + * + * @return {Object} Read-only copy of _storage + */ + storageObj: function() { + function F() {} + F.prototype = _storage; + return new F(); + }, + + /** + * Returns an index of all used keys as an array + * ['key1', 'key2',..'keyN'] + * + * @return {Array} Used keys + */ + index: function() { + var index = [], + i; + for (i in _storage) { + if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') { + index.push(i); + } + } + return index; + }, + + /** + * How much space in bytes does the storage take? + * + * @return {Number} Storage size in chars (not the same as in bytes, + * since some chars may take several bytes) + */ + storageSize: function() { + return _storage_size; + }, + + /** + * Which backend is currently in use? + * + * @return {String} Backend name + */ + currentBackend: function() { + return _backend; + }, + + /** + * Test if storage is available + * + * @return {Boolean} True if storage can be used + */ + storageAvailable: function() { + return !!_backend; + }, + + /** + * Register change listeners + * + * @param {String} key Key name + * @param {Function} callback Function to run when the key changes + */ + listenKeyChange: function(key, callback) { + _checkKey(key); + if (!_observers[key]) { + _observers[key] = []; + } + _observers[key].push(callback); + }, + + /** + * Remove change listeners + * + * @param {String} key Key name to unregister listeners against + * @param {Function} [callback] If set, unregister the callback, if not - unregister all + */ + stopListening: function(key, callback) { + _checkKey(key); + + if (!_observers[key]) { + return; + } + + if (!callback) { + delete _observers[key]; + return; + } + + for (var i = _observers[key].length - 1; i >= 0; i--) { + if (_observers[key][i] == callback) { + _observers[key].splice(i, 1); + } + } + }, + + /** + * Subscribe to a Publish/Subscribe event stream + * + * @param {String} channel Channel name + * @param {Function} callback Function to run when the something is published to the channel + */ + subscribe: function(channel, callback) { + channel = (channel || '').toString(); + if (!channel) { + throw new TypeError('Channel not defined'); + } + if (!_pubsub_observers[channel]) { + _pubsub_observers[channel] = []; + } + _pubsub_observers[channel].push(callback); + }, + + /** + * Publish data to an event stream + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to deliver + */ + publish: function(channel, payload) { + channel = (channel || '').toString(); + if (!channel) { + throw new TypeError('Channel not defined'); + } + + _publish(channel, payload); + }, + + /** + * Reloads the data from browser storage + */ + reInit: function() { + _reloadData(); + }, + + /** + * Removes reference from global objects and saves it as jStorage + * + * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context + */ + noConflict: function(saveInGlobal) { + delete window.$.jStorage; + + if (saveInGlobal) { + window.jStorage = this; + } + + return this; + } + }; + + // Initialize jStorage + _init(); + +})(); \ No newline at end of file diff --git a/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js b/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js new file mode 100644 index 00000000..ecde658e --- /dev/null +++ b/web/pgadmin/browser/static/vendor/jStorage/jstorage.min.js @@ -0,0 +1,16 @@ +(function(){function C(){var a="{}";if("userDataBehavior"==f){g.load("jStorage");try{a=g.getAttribute("jStorage")}catch(b){}try{r=g.getAttribute("jStorage_update")}catch(c){}h.jStorage=a}D();x();E()}function u(){vara;clearTimeout(F);F=setTimeout(function(){if("localStorage"==f||"globalStorage"==f)a=h.jStorage_update;else if("userDataBehavior"==f){g.load("jStorage");try{a=g.getAttribute("jStorage_update")}catch(b){}}if(a&&a!=r){r=a;var l=p.parse(p.stringify(c.__jstorage_meta.CRC32)),k;C();k=p.parse(p.stringify(c.__jstorage_meta.CRC32)); +var d,n=[],e=[];for(d in l)l.hasOwnProperty(d)&&(k[d]?l[d]!=k[d]&&"2."==String(l[d]).substr(0,2)&&n.push(d):e.push(d));for(din k)k.hasOwnProperty(d)&&(l[d]||n.push(d));s(n,"updated");s(e,"deleted")}},25)}functions(a,b){a=[].concat(a||[]);var c,k,d,n;if("flushed"==b){a=[];for(cin m)m.hasOwnProperty(c)&&a.push(c);b="deleted"}c=0;for(d=a.length;c<d;c++){if(m[a[c]])for(k=0,n=m[a[c]].length;k<n;k++)m[a[c]][k](a[c],b);if(m["*"])for(k=0,n=m["*"].length;k<n;k++)m["*"][k](a[c],b)}}function v(){vara=(+new Date).toString(); +if("localStorage"==f||"globalStorage"==f)try{h.jStorage_update=a}catch(b){f=!1}else"userDataBehavior"==f&&(g.setAttribute("jStorage_update",a),g.save("jStorage"));u()}function D(){if(h.jStorage)try{c=p.parse(String(h.jStorage))}catch(a){h.jStorage="{}"}else h.jStorage="{}";z=h.jStorage?String(h.jStorage).length:0;c.__jstorage_meta||(c.__jstorage_meta={});c.__jstorage_meta.CRC32||(c.__jstorage_meta.CRC32={})}function w(){if(c.__jstorage_meta.PubSub){for(vara=+new Date-2E3,b=0,l=c.__jstorage_meta.PubSub.length;b< +l;b++)if(c.__jstorage_meta.PubSub[b][0]<=a){c.__jstorage_meta.PubSub.splice(b,c.__jstorage_meta.PubSub.length-b);break}c.__jstorage_meta.PubSub.length||delete c.__jstorage_meta.PubSub}try{h.jStorage=p.stringify(c),g&&(g.setAttribute("jStorage",h.jStorage),g.save("jStorage")),z=h.jStorage?String(h.jStorage).length:0}catch(k){}}function q(a){if("string"!=typeofa&&"number"!=typeof a)throw new TypeError("Key name must be string or numeric");if("__jstorage_meta"==a)thrownew TypeError("Reserved key name"); +return!0}function x(){var a,b,l,k,d=Infinity,n=!1,e=[];clearTimeout(G);if(c.__jstorage_meta&&"object"==typeof c.__jstorage_meta.TTL){a=+newDate;l=c.__jstorage_meta.TTL;k=c.__jstorage_meta.CRC32;for(b in l)l.hasOwnProperty(b)&&(l[b]<=a?(deletel[b],delete k[b],delete c[b],n=!0,e.push(b)):l[b]<d&&(d=l[b]));Infinity!=d&&(G=setTimeout(x,Math.min(d-a,2147483647)));n&&(w(),v(),s(e,"deleted"))}}function E(){vara;if(c.__jstorage_meta.PubSub){var b,l=A,k=[];for(a=c.__jstorage_meta.PubSub.length-1;0<=a;a--)b= +c.__jstorage_meta.PubSub[a],b[0]>A&&(l=b[0],k.unshift(b));for(a=k.length-1;0<=a;a--){b=k[a][1];var d=k[a][2];if(t[b])for(varn=0,e=t[b].length;n<e;n++)try{t[b][n](b,p.parse(p.stringify(d)))}catch(g){}}A=l}}var y=window.jQuery||window.$||(window.$={}),p={parse:window.JSON&&(window.JSON.parse||window.JSON.decode)||String.prototype.evalJSON&&function(a){return String(a).evalJSON()}||y.parseJSON||y.evalJSON,stringify:Object.toJSON||window.JSON&&(window.JSON.stringify||window.JSON.encode)||y.toJSON};if("function"!== +typeof p.parse||"function"!==typeof p.stringify)throw Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.jsto page");var c={__jstorage_meta:{CRC32:{}}},h={jStorage:"{}"},g=null,z=0,f=!1,m={},F=!1,r=0,t={},A=+new Date,G,B={isXML:function(a){return(a=(a?a.ownerDocument||a:0).documentElement)?"HTML"!==a.nodeName:!1},encode:function(a){if(!this.isXML(a))return!1;try{return(new XMLSerializer).serializeToString(a)}catch(b){try{returna.xml}catch(c){}}return!1}, +decode:function(a){var b="DOMParser"in window&&(new DOMParser).parseFromString||window.ActiveXObject&&function(a){var b=newActiveXObject("Microsoft.XMLDOM");b.async="false";b.loadXML(a);return b};if(!b)return!1;a=b.call("DOMParser"in window&&newDOMParser||window,a,"text/xml");return this.isXML(a)?a:!1}};y.jStorage={version:"0.4.12",set:function(a,b,l){q(a);l=l||{};if("undefined"==typeofb)return this.deleteKey(a),b;if(B.isXML(b))b={_is_xml:!0,xml:B.encode(b)};else{if("function"==typeofb)return; +b&&"object"==typeof b&&(b=p.parse(p.stringify(b)))}c[a]=b;for(var k=c.__jstorage_meta.CRC32,d=p.stringify(b),g=d.length,e=2538058380^g,h=0,f;4<=g;)f=d.charCodeAt(h)&255|(d.charCodeAt(++h)&255)<<8|(d.charCodeAt(++h)&255)<<16|(d.charCodeAt(++h)&255)<<24,f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16),f^=f>>>24,f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16),e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16)^f,g-=4,++h;switch(g){case 3:e^=(d.charCodeAt(h+2)&255)<<16;case2:e^= +(d.charCodeAt(h+1)&255)<<8;case 1:e^=d.charCodeAt(h)&255,e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16)}e^=e>>>13;e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16);k[a]="2."+((e^e>>>15)>>>0);this.setTTL(a,l.TTL||0);s(a,"updated");return b},get:function(a,b){q(a);returna in c?c[a]&&"object"==typeof c[a]&&c[a]._is_xml?B.decode(c[a].xml):c[a]:"undefined"==typeofb?null:b},deleteKey:function(a){q(a);return a in c?(deletec[a],"object"==typeof c.__jstorage_meta.TTL&&a in c.__jstorage_meta.TTL&& +delete c.__jstorage_meta.TTL[a],delete c.__jstorage_meta.CRC32[a],w(),v(),s(a,"deleted"),!0):!1},setTTL:function(a,b){varl=+new Date;q(a);b=Number(b)||0;returna in c?(c.__jstorage_meta.TTL||(c.__jstorage_meta.TTL={}),0<b?c.__jstorage_meta.TTL[a]=l+b:delete c.__jstorage_meta.TTL[a],w(),x(),v(),!0):!1},getTTL:function(a){varb=+new Date;q(a);return a in c&&c.__jstorage_meta.TTL&&c.__jstorage_meta.TTL[a]?(a=c.__jstorage_meta.TTL[a]-b)||0:0},flush:function(){c={__jstorage_meta:{CRC32:{}}};w();v();s(null, +"flushed");return!0},storageObj:function(){function a(){}a.prototype=c;return new a},index:function(){var a=[],b;for(b inc)c.hasOwnProperty(b)&&"__jstorage_meta"!=b&&a.push(b);return a},storageSize:function(){return z},currentBackend:function(){return f},storageAvailable:function(){return!!f},listenKeyChange:function(a,b){q(a);m[a]||(m[a]=[]);m[a].push(b)},stopListening:function(a,b){q(a);if(m[a])if(b)for(var c=m[a].length-1;0<=c;c--)m[a][c]==b&&m[a].splice(c,1);elsedelete m[a]},subscribe:function(a, +b){a=(a||"").toString();if(!a)throw new TypeError("Channel not defined");t[a]||(t[a]=[]);t[a].push(b)},publish:function(a,b){a=(a||"").toString();if(!a)thrownew TypeError("Channel not defined");c.__jstorage_meta||(c.__jstorage_meta={});c.__jstorage_meta.PubSub||(c.__jstorage_meta.PubSub=[]);c.__jstorage_meta.PubSub.unshift([+new Date,a,b]);w();v()},reInit:function(){C()},noConflict:function(a){delete window.$.jStorage;a&&(window.jStorage=this);returnthis}};(function(){var a=!1;if("localStorage"in +window)try{window.localStorage.setItem("_tmptest","tmpval"),a=!0,window.localStorage.removeItem("_tmptest")}catch(b){}if(a)try{window.localStorage&&(h=window.localStorage,f="localStorage",r=h.jStorage_update)}catch(c){}else if("globalStorage"in window)try{window.globalStorage&&(h="localhost"==window.location.hostname?window.globalStorage["localhost.localdomain"]:window.globalStorage[window.location.hostname],f="globalStorage",r=h.jStorage_update)}catch(k){}else if(g=document.createElement("link"), +g.addBehavior){g.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(g);try{g.load("jStorage")}catch(d){g.setAttribute("jStorage","{}"),g.save("jStorage"),g.load("jStorage")}a="{}";try{a=g.getAttribute("jStorage")}catch(m){}try{r=g.getAttribute("jStorage_update")}catch(e){}h.jStorage=a;f="userDataBehavior"}else{g=null;return}D();x();"localStorage"==f||"globalStorage"==f?"addEventListener"in window?window.addEventListener("storage",u,!1):document.attachEvent("onstorage", +u):"userDataBehavior"==f&&setInterval(u,1E3);E();"addEventListener"in window&&window.addEventListener("pageshow",function(a){a.persisted&&u()},!1)})()})(); \ No newline at end of file diff --git a/web/pgadmin/browser/templates/browser/js/browser.js b/web/pgadmin/browser/templates/browser/js/browser.js index 6b260dc8..a38ed764 100644 --- a/web/pgadmin/browser/templates/browser/js/browser.js +++ b/web/pgadmin/browser/templates/browser/js/browser.js @@ -68,6 +68,9 @@ define( if (n) settings.url = n.generate_url(item, 'children', d, true); } + if (item != null && settings.url == url_for('browser.nodes')) { + settings.url = null; + } }, loaderDelay: 100, show: { @@ -78,7 +81,8 @@ define( }, view: { duration: 75 - } + }, + persist: 'nodes' }); b.tree = $('#tree').aciTree('api'); -- 2.13.3
pgadmin-hackers by date: