From 3bad5e56c540ddc19b4ffc27a73611e21366f539 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Tue, 28 Aug 2012 02:45:32 +0400 Subject: [PATCH] minor edits... Signed-off-by: Alex A. Naanou --- ui/TODO.otl | 25 +- ui/fullscreen.html | 1 + ui/jstorage.js | 770 +++++++++++++++++++++++++++++++++++++++++++++ ui/keybindings.js | 7 +- 4 files changed, 794 insertions(+), 9 deletions(-) create mode 100755 ui/jstorage.js diff --git a/ui/TODO.otl b/ui/TODO.otl index b09b511c..2093545b 100755 --- a/ui/TODO.otl +++ b/ui/TODO.otl @@ -1,8 +1,15 @@ Priority work - [_] 74% Preview II + [_] 62% Preview II + [_] 50% load folder + [X] drag'n'drop + [_] open dilog + [_] 0% load state + [_] auto: last state + [_] manual [_] 0% save state - [_] minomal: Local Storage - [_] local JSON + [_] minimal: Local Storage + | XXX does not seem to work in CEF + [_] local JSON (file) [_] CouchDB [_] 49% native client [_] 33% Windows (CEF / CEFPython) @@ -127,14 +134,14 @@ Priority work [X] 100% actions [X] bug: shifting up to new ribbon pushes the current row down... | before starting on a fix, need to cleanup the code from old hacks and workarounds... - [_] 29% Preview II (optional features) + [_] 40% Preview II (optional features) [_] 0% PhoneGap + Android Issues: + [_] scrolling in overlays does not work [_] half the keyboard is not working... [_] screen buttons are very laggy | while swipe works super fast... [_] .dblclick(...) does not work... [_] .dragable(...) does not work... - [_] scrolling in overlays does not work [_] slideshow... [_] make keyboeard handler mode-aware... | this is needed to disable navigation keys in setup-mode, for example... @@ -142,7 +149,7 @@ Priority work [X] JSON loader/unloader [_] file reader/writer [_] actual file loading (Target-specific) - [_] flip ribbons relative to current -- reverse order + [X] flip ribbons relative to current -- reverse order [_] fade transition in single image mode... [_] "show all promoted/demoted images" mode | display images below or above but in a more transparent @@ -174,8 +181,10 @@ Priority work [_] 50% return to current image / home (after drag) [_] button [X] keyboard - [_] double tap/click to zoom (a-la iPad) - | fit <-> actual pixels (max) + [X] double tap/click to zoom (a-la iPad) + | ribbon <-> single image mode + | + | XXX does not work in android... [_] pinch to zoom [X] BUG: rendering error when current ribbon images opacity is 1... | This happens when: diff --git a/ui/fullscreen.html b/ui/fullscreen.html index 9ad3430d..b98c701b 100755 --- a/ui/fullscreen.html +++ b/ui/fullscreen.html @@ -29,6 +29,7 @@ body { + diff --git a/ui/jstorage.js b/ui/jstorage.js new file mode 100755 index 00000000..ad9ec538 --- /dev/null +++ b/ui/jstorage.js @@ -0,0 +1,770 @@ +/* + * ----------------------------- JSTORAGE ------------------------------------- + * Simple local storage wrapper to save data on the browser side, supporting + * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ + * + * Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com + * Project homepage: www.jstorage.info + * + * Licensed under MIT-style license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 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 OR COPYRIGHT HOLDERS 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. + */ + + (function(){ + var + /* jStorage version */ + JSTORAGE_VERSION = "0.2.3", + + /* 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: + window.JSON && (window.JSON.stringify || window.JSON.encode) || + Object.toJSON || + $.toJSON + }; + + // Break if no JSON support was found + if(!JSON.parse || !JSON.stringify){ + 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 = {}, + + /* 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 */ + _observerTimeout = false, + + /* last update time */ + _observerUpdate = 0, + + /* Next check for TTL */ + _ttl_timeout, + + /* crc32 table */ + _crc32Table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 "+ + "0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 "+ + "6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 "+ + "FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 "+ + "A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 "+ + "32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 "+ + "56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 "+ + "C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 "+ + "E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 "+ + "6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 "+ + "12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE "+ + "A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 "+ + "DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 "+ + "5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 "+ + "2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF "+ + "04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 "+ + "7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 "+ + "FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 "+ + "A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C "+ + "36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 "+ + "5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 "+ + "C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 "+ + "EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D "+ + "7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 "+ + "18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 "+ + "A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A "+ + "D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A "+ + "53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 "+ + "2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D", + + /** + * 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"; + _observerUpdate = _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) { + _storage_service = window.globalStorage[window.location.hostname]; + _backend = "globalStorage"; + _observerUpdate = _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); + + _storage_elm.load("jStorage"); + + var data = "{}"; + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + + try{ + _observerUpdate = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + + _storage_service.jStorage = data; + _backend = "userDataBehavior"; + }else{ + _storage_elm = null; + return; + } + } + + _load_storage(); + + // remove dead keys + _handleTTL(); + + // start listening for changes + _setupObserver(); + } + + function _reloadData(){ + var data = "{}"; + + if(_backend == "userDataBehavior"){ + _storage_elm.load("jStorage"); + + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + + try{ + _observerUpdate = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + + _storage_service.jStorage = data; + } + + _load_storage(); + + // remove dead keys + _handleTTL(); + } + + /** + * 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(_observerTimeout); + _observerTimeout = 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 != _observerUpdate){ + _observerUpdate = updateTime; + _checkUpdatedKeys(); + } + + }, 100); + } + + /** + * 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]){ + 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 || []); + if(action == "flushed"){ + keys = []; + for(var key in _observers){ + if(_observers.hasOwnProperty(key)){ + keys.push(key); + } + } + action = "deleted"; + } + for(var i=0, len = keys.length; i>> 8)^x; + } + return crc^(-1); + } + + ////////////////////////// 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 + * @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] = _crc32(JSON.stringify(value)); + + 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 && + _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); + } + } + }, + + /** + * Reloads the data from browser storage + */ + reInit: function(){ + _reloadData(); + } + }; + + // Initialize jStorage + _init(); + +})(); \ No newline at end of file diff --git a/ui/keybindings.js b/ui/keybindings.js index 5c709d16..8f9e5888 100755 --- a/ui/keybindings.js +++ b/ui/keybindings.js @@ -1,10 +1,12 @@ /*********************************************************************/ // NOTE: use String.fromCharCode(code)... -// list of keys to be ignored by handler... +// list of keys to be ignored by handler but still handled by the browser... var ignorekeys = [ 116, // F5 123, // F12 ] + + var keybindings = { // togglable modes and options... 191: { @@ -88,6 +90,9 @@ var keybindings = { 'ctrl+shift': ImageGrid.shiftImageUpNewRibbon // ctrl-shift-Up }, + // misc actions... + 82: ImageGrid.reverseImageOrder, // r + // ignore the modifiers (shift, alt, ctrl, caps)... 16: function(){},