From 8d446609fd2614862e23ef265d0addf4fca611e4 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Tue, 28 May 2013 02:17:24 +0400 Subject: [PATCH] lots of work on file loading and saving, now almost up to spec, still needs refactoring... Signed-off-by: Alex A. Naanou --- ui/Gen3-TODO.otl | 3 + ui/data.js | 689 +++++++++++++++++++++++++++------------------ ui/index.html | 5 +- ui/keybindings3.js | 2 + ui/lib/jli.js | 28 ++ 5 files changed, 453 insertions(+), 274 deletions(-) diff --git a/ui/Gen3-TODO.otl b/ui/Gen3-TODO.otl index 7ccc14af..aa97e3fd 100755 --- a/ui/Gen3-TODO.otl +++ b/ui/Gen3-TODO.otl @@ -23,8 +23,11 @@ [_] BUG: changing window size in single image modes messes things up... | until we cycle to ribbon mode and back... [_] ASAP: load/view un-cached directories... + [_] show only one ribbon mode + | should this have up/down navigation? [_] slideshow mode... [_] import fav dirs (wo. index)... + [_] image sorting (reverse/date/name/...) [_] add ability to save/load ranges of images and the structures around them | e.g.load image 100 to current ribbon -> will load 100 images | for current ribbon and all the in between images from other diff --git a/ui/data.js b/ui/data.js index 239bb346..7ed5adee 100755 --- a/ui/data.js +++ b/ui/data.js @@ -57,10 +57,8 @@ var DATA = { // the images object, this is indexed by image GID and contains all // the needed data... -// XXX should we split this out? var IMAGES = {} -// True if images is modified and needs saving... -var IMAGES_DIRTY = false +var IMAGES_UPDATED = [] var DATA_ATTR = 'DATA' @@ -252,15 +250,17 @@ function getGIDBefore(gid, ribbon, search){ } +// Get a "count" of GIDs starting with a given gid ("from") +// +// NOTE: this will not include the 'from' GID in the resulting list, +// unless inclusive is set to true. // NOTE: count can be either negative or positive, this will indicate // load direction... -// NOTE: this will not include the 'from' GID in the resulting list... -// NOTE: this can calculate the ribbon number if an image can be only -// in one ribbon... +// NOTE: this can calculate the ribbon number where the image is located. // NOTE: if an image can be in more than one ribbon, one MUST suply the // correct ribbon number... +// // XXX do we need more checking??? -// XXX inclusive can not be false, only null or true... function getImageGIDs(from, count, ribbon, inclusive){ if(count == 0){ return [] @@ -292,8 +292,6 @@ function getImageGIDs(from, count, ribbon, inclusive){ // Select best preview by size... // // NOTE: this will use the original if everything else is smaller... -// -// XXX make this both relative and absolute URL compatible... function getBestPreview(gid, size){ size = size == null ? getVisibleImageSize('max') : size var s @@ -311,7 +309,6 @@ function getBestPreview(gid, size){ } } return { - //url: url, url: normalizePath(url), size: preview_size } @@ -320,7 +317,7 @@ function getBestPreview(gid, size){ // Normalize the path... // -// This will do: +// This will: // - convert windows absolute paths 'X:\...' -> 'file:///X:/...' // - if mode is 'absolute': // - return absolute paths as-is @@ -333,8 +330,6 @@ function getBestPreview(gid, size){ // - return relative paths as-is // // NOTE: mode can be either 'absolute' (default) or 'relative'... -// -// XXX need to account for '.' base function normalizePath(url, base, mode){ mode = mode == null ? 'absolute' : mode base = base == null ? BASE_URL : base @@ -347,7 +342,7 @@ function normalizePath(url, base, mode){ url = 'file:///' + url } - // absolute path... + // we got absolute path... if(/^(file|http|https):\/\/.*$/.test(url)){ // check if we start with base, and remove it if so... if(mode == 'relative' && url.substring(0, base.length) == base){ @@ -375,6 +370,56 @@ function normalizePath(url, base, mode){ +/********************************************************************** +* Format conversion +*/ + +// Convert legacy Gen1 data format to current Gen3 (v2.0) +function convertDataGen1(data, cmp){ + var res = { + data: { + version: '2.0', + current: null, + ribbons: [], + order: [], + }, + images: {} + } + cmp = cmp == null ? + function(a, b){ + return imageDateCmp(a, b, res.images) + } + : cmp + var ribbons = res.data.ribbons + var order = res.data.order + var images = res.images + + // position... + res.data.current = data.position + + // ribbons and images... + $.each(data.ribbons, function(i, input_images){ + var ribbon = [] + ribbons.push(ribbon) + for(var id in input_images){ + var image = input_images[id] + ribbon.push(id) + order.push(id) + images[id] = image + } + ribbon.sort(cmp) + }) + + order.sort(cmp) + + // XXX STUB + res.data.current = order[0] + + return res +} + + + /********************************************************************** * Loaders */ @@ -535,6 +580,8 @@ function loadImagesAround(ref_gid, count, ribbon){ } +// Roll ribbon and load new images in the updated section. +// // NOTE: this is signature-compatible with rollRibbon... // NOTE: this will load data ONLY if it is available, otherwise this // will have no effect... @@ -604,50 +651,6 @@ function loadData(images_per_screen){ } -function convertDataGen1(data, cmp){ - var res = { - data: { - version: '2.0', - current: null, - ribbons: [], - order: [], - }, - images: {} - } - cmp = cmp == null ? - function(a, b){ - return imageDateCmp(a, b, res.images) - } - : cmp - var ribbons = res.data.ribbons - var order = res.data.order - var images = res.images - - // position... - res.data.current = data.position - - // ribbons and images... - $.each(data.ribbons, function(i, input_images){ - var ribbon = [] - ribbons.push(ribbon) - for(var id in input_images){ - var image = input_images[id] - ribbon.push(id) - order.push(id) - images[id] = image - } - ribbon.sort(cmp) - }) - - order.sort(cmp) - - // XXX STUB - res.data.current = order[0] - - return res -} - - function loadSettings(){ toggleTheme(SETTINGS['theme']) @@ -663,218 +666,6 @@ function loadSettings(){ -/********************************************************************** -* localStorage -* -* XXX should we use jStorage here? -*/ - -function loadLocalStorageData(attr){ - attr = attr == null ? DATA_ATTR : attr - var data = localStorage[attr] - if(data == null){ - data = '{}' - } - return { - data: JSON.parse(data), - base_url: localStorage[attr + '_BASE_URL'], - } -} - - -function saveLocalStorageData(attr){ - attr = attr == null ? DATA_ATTR : attr - localStorage[attr] = JSON.stringify(DATA) - localStorage[attr + '_BASE_URL'] = BASE_URL -} - - -function loadLocalStorageImages(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_IMAGES' - var images = localStorage[attr] - if(images == null){ - images = '{}' - } - return JSON.parse(images) -} - - -function saveLocalStorageImages(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_IMAGES' - localStorage[attr] = JSON.stringify(IMAGES) -} - - -// generic save/load... -function loadLocalStorage(attr){ - attr = attr == null ? DATA_ATTR : attr - var d = loadLocalStorageData(attr) - BASE_URL = d.base_url - DATA = d.data - IMAGES = loadLocalStorageImages(attr) - return loadData() -} - - -function saveLocalStorage(attr){ - attr = attr == null ? DATA_ATTR : attr - saveLocalStorageData(attr) - saveLocalStorageImages(attr) -} - - -function loadLocalStorageMarks(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_MARKED' - var marked = localStorage[attr] - if(marked == null){ - marked = '[]' - } - MARKED = JSON.parse(marked) - return loadData() -} - - -function saveLocalStorageMarks(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_MARKED' - localStorage[attr] = JSON.stringify(MARKED) -} - - -function loadLocalStorageSettings(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_SETTINGS' - SETTINGS = JSON.parse(localStorage[attr]) - - loadSettings() -} - - -function saveLocalStorageSettings(attr){ - attr = attr == null ? DATA_ATTR : attr - attr += '_SETTINGS' - localStorage[attr] = JSON.stringify(SETTINGS) -} - - - -/********************************************************************** -* Extension API (CEF/PhoneGap/...) -*/ - -function loadFileImages(path, callback){ - return $.getJSON(path) - .done(function(json){ - IMAGES = json - localStorage[DATA_ATTR + '_IMAGES_FILE'] = path - console.log('Loaded IMAGES...') - - callback != null && callback() - }) - .fail(function(){ - console.error('ERROR LOADING:', path) - }) -} - - -function loadFile(data_path, image_path, callback){ - var base = data_path.split(CACHE_DIR)[0] - base = base == data_path ? '.' : base - // CEF - return $.getJSON(data_path) - .done(function(json){ - BASE_URL = base - // legacy format... - if(json.version == null){ - json = convertDataGen1(json) - DATA = json.data - IMAGES = json.images - // XXX load marked data... - MARKED = [] - loadData() - - // version 2.0 - // XXX needs a more flexible protocol... - } else if(json.version == '2.0') { - DATA = json - if(image_path != null){ - loadFileImages(normalizePath(image_path, base)) - .done(function(){ - loadData() - - callback != null && callback() - }) - } else if(DATA.image_file != null) { - loadFileImages(normalizePath(DATA.image_file, base)) - .done(function(){ - loadData() - - callback != null && callback() - }) - } - - // unknown format... - } else { - console.error('unknown format.') - return - } - }) - .fail(function(){ - console.error('ERROR LOADING:', data_path) - }) -} - - -function saveFile(name){ - // CEF - if(window.CEF_dumpJSON != null){ - if(DATA.image_file == null){ - DATA.image_file = name + '-images.json' - } - //CEF_dumpJSON(DATA.image_file, IMAGES) - // XXX this will overwrite the images... - //CEF_dumpJSON(name + '-images.json', IMAGES) - //DATA.image_file = name + '-images.json' - CEF_dumpJSON(name + '-data.json', DATA) - CEF_dumpJSON(name + '-marked.json', MARKED) - - // PhoneGap - } else if(false) { - // XXX - } -} - - -function openImage(){ - // CEF - if(window.CEF_runSystem != null){ - // XXX if path is not present try and open the biggest preview... - return CEF_runSystem(normalizePath(IMAGES[getImageGID()].path, BASE_URL)) - - // PhoneGap - } else if(false) { - // XXX - } -} - - -// XXX need revision... -function loadDir(path){ - return loadFile(BASE_URL +'/data.json') - .fail(function(){ - loadFile(BASE_URL +'/'+ CACHE_DIR +'/data.json') - .fail(function(){ - // XXX load plain images... - // XXX - }) - }) -} - - - /********************************************************************** * Image caching... */ @@ -913,6 +704,358 @@ function preCacheAllRibbons(){ +/********************************************************************** +* localStorage +* +* XXX should we use jStorage here? +*/ + +function loadLocalStorageData(attr){ + attr = attr == null ? DATA_ATTR : attr + var data = localStorage[attr] + if(data == null){ + data = '{}' + } + return { + data: JSON.parse(data), + base_url: localStorage[attr + '_BASE_URL'], + } +} +function saveLocalStorageData(attr){ + attr = attr == null ? DATA_ATTR : attr + localStorage[attr] = JSON.stringify(DATA) + localStorage[attr + '_BASE_URL'] = BASE_URL +} + + +function loadLocalStorageImages(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_IMAGES' + var images = localStorage[attr] + if(images == null){ + images = '{}' + } + return JSON.parse(images) +} +function saveLocalStorageImages(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_IMAGES' + localStorage[attr] = JSON.stringify(IMAGES) +} + + +function loadLocalStorageMarks(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_MARKED' + var marked = localStorage[attr] + if(marked == null){ + marked = '[]' + } + MARKED = JSON.parse(marked) + return loadData() +} +function saveLocalStorageMarks(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_MARKED' + localStorage[attr] = JSON.stringify(MARKED) +} + + +function loadLocalStorageSettings(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_SETTINGS' + SETTINGS = JSON.parse(localStorage[attr]) + + loadSettings() +} +function saveLocalStorageSettings(attr){ + attr = attr == null ? DATA_ATTR : attr + attr += '_SETTINGS' + localStorage[attr] = JSON.stringify(SETTINGS) +} + + +// generic save/load... +function loadLocalStorage(attr){ + attr = attr == null ? DATA_ATTR : attr + var d = loadLocalStorageData(attr) + BASE_URL = d.base_url + DATA = d.data + IMAGES = loadLocalStorageImages(attr) + return loadData() +} +function saveLocalStorage(attr){ + attr = attr == null ? DATA_ATTR : attr + saveLocalStorageData(attr) + saveLocalStorageImages(attr) +} + + + +/********************************************************************** +* File storage (Extension API -- CEF/PhoneGap/...) +* +* XXX need to cleanup this section... +*/ + +// CEF +if(window.CEF_dumpJSON != null){ + var dumpJSON = CEF_dumpJSON + var listDir = CEF_listDir + var removeFile = CEF_removeFile + var runSystem = CEF_runSystem + +// PhoneGap +} else if(false) { + // XXX + var dumpJSON = null + var listDir = null + var removeFile = null + var runSystem = null +} + + +function loadFileImages(path, no_load_diffs, callback){ + + if(window.listDir == null){ + no_load_diffs = true + } + + if(path == null){ + var base = normalizePath(CACHE_DIR) + var path = $.map(listDir(base), function(e){ + return /.*-images.json$/.test(e) ? e : null + }).sort().reverse()[0] + path = path == null ? 'images.json' : path + + console.log('Loading:', path) + + path = base +'/'+ path + + } else { + path = normalizePath(path) + // XXX need to account for paths without a CACHE_DIR + var base = path.split(CACHE_DIR)[0] + base += '/'+ CACHE_DIR + } + + var diff_data = {} + var diff = true + + // XXX what are we going to do if base == path, i.e. no cache dir??? + + + // XXX no error handling if one of the diff loads fail... + if(!no_load_diffs){ + var diffs = [diff_data] + var diffs_names = $.map(listDir(base), function(e){ + return /.*-images-diff.json$/.test(e) ? e : null + }).sort() + diff = $.when.apply(null, $.map(diffs_names, function(e, i){ + return $.getJSON(normalizePath(base +'/'+ e)) + .done(function(data){ + diffs[i+1] = data + console.log('Loaded:', e) + }) + })) + .then(function(){ + $.extend.apply(null, diffs) + }) + } + + return $.when(diff, $.getJSON(path)) + .done(function(_, json){ + json = json[0] + $.extend(json, diff_data) + IMAGES = json + + //localStorage[DATA_ATTR + '_IMAGES_FILE'] = path + + console.log('Loaded IMAGES...') + + callback != null && callback() + }) + .fail(function(){ + console.error('ERROR LOADING:', path) + }) +} + + +// XXX make this load a default data filename... +// XXX look into the CACHE_DIR if not explicitly given... +function loadFileState(data_path, image_path, callback){ + var base = data_path.split(CACHE_DIR)[0] + base = base == data_path ? '.' : base + + return $.getJSON(data_path) + .done(function(json){ + BASE_URL = base + // legacy format... + if(json.version == null){ + json = convertDataGen1(json) + DATA = json.data + IMAGES = json.images + // XXX load marked data... + MARKED = [] + loadData() + + // version 2.0 + // XXX needs a more flexible protocol... + } else if(json.version == '2.0') { + DATA = json + if(image_path != null){ + loadFileImages(normalizePath(image_path, base)) + .done(function(){ + loadData() + + callback != null && callback() + }) + } else if(DATA.image_file != null) { + loadFileImages(normalizePath(DATA.image_file, base)) + .done(function(){ + loadData() + + callback != null && callback() + }) + } else { + loadFileImages(null) + .done(function(){ + loadData() + + callback != null && callback() + }) + } + + // unknown format... + } else { + console.error('unknown format.') + return + } + }) + .fail(function(){ + console.error('ERROR LOADING:', data_path) + }) +} + + +// Save current images list... +// +// NOTE: this will save the merged images and remove the diff files... +// NOTE: if an explicit name is given then this will not remove anything. +// NOTE: if not explicit name is given this will save to the current +// cache dir. +function saveFileImages(name){ + var remove_diffs = (name == null) + name = name == null ? normalizePath(CACHE_DIR +'/'+ Date.timeStamp()) : name + + // CEF + if(window.dumpJSON == null){ + console.error('Can\'t save to file.') + return + } + + // remove the diffs... + if(remove_diffs){ + $.each($.map(listDir(normalizePath(CACHE_DIR)), function(e){ + return /.*-images-diff.json$/.test(e) ? e : null + }), function(i, e){ + console.log('removeing:', e) + removeFile(normalizePath(CACHE_DIR +'/'+ e)) + }) + IMAGES_UPDATED = [] + } + + dumpJSON(name + '-images.json', IMAGES) + //DATA.image_file = normalizePath(name + '-images.json', null, 'relative') +} + + +function saveFileState(name, no_normalize_path){ + name = name == null ? Date.timeStamp() : name + + if(!no_normalize_path){ + name = normalizePath(CACHE_DIR +'/'+ name) + + // write .image_file only if saving data to a non-cache dir... + // XXX check if this is correct... + } else { + if(DATA.image_file == null){ + DATA.image_file = name + '-images.json' + } + } + + dumpJSON(name + '-data.json', DATA) + dumpJSON(name + '-marked.json', MARKED) + + // save the updated images... + if(IMAGES_UPDATED.length > 0){ + var updated = {} + $.each(IMAGES_UPDATED, function(i, e){ + updated[e] = IMAGES[e] + }) + dumpJSON(name + '-images-diff.json', updated) + IMAGES_UPDATED = [] + } +} + + +// Open image in an external editor/viewer +// +// NOTE: this will open the default editor/viewer. +function openImage(){ + // CEF + if(window.runSystem == null){ + console.error('Can\'t run external programs.') + return + } + + // XXX if path is not present try and open the biggest preview... + return runSystem(normalizePath(IMAGES[getImageGID()].path, BASE_URL)) +} + + +// XXX need revision... +function loadDir(path){ + + if(window.CEF_listDir != null){ + var listDir = CEF_listDir + + // PhoneGap + } else if(false) { + // XXX + + } else { + no_load_diffs = true + } + + path = normalizePath(path) + + var files = listDir(path) + var data = $.map(files, function(e){ + return /.*-data.json$/.test(e) ? e : null + }).sort().reverse()[0] + data = (data == null && files.indexOf('data.json') >= 0) ? 'data.json' : data + + // look in the cache dir... + if(data == null){ + path += '/' + CACHE_DIR + + files = listDir(path) + data = $.map(listDir(path), function(e){ + return /.*-data.json$/.test(e) ? e : null + }).sort().reverse()[0] + data = (data == null && files.indexOf('data.json') >= 0) ? 'data.json' : data + } + + console.log('Loading:', data) + + data = path + '/' + data + + return loadFileState(data) +} + + + /********************************************************************** * Setup */ @@ -1070,7 +1213,9 @@ function setupDataBindings(viewer){ var orientation = img.attr('orientation') IMAGES[gid].orientation = orientation - IMAGES_DIRTY = true + if(IMAGES_UPDATED.indexOf(gid) == -1){ + IMAGES_UPDATED.push(gid) + } }) }) @@ -1137,4 +1282,4 @@ function setupDataBindings(viewer){ /********************************************************************** -* vim:set ts=4 sw=4 : */ +* vim:set ts=4 sw=4 spell : */ diff --git a/ui/index.html b/ui/index.html index 6985950c..8b61dc67 100755 --- a/ui/index.html +++ b/ui/index.html @@ -458,8 +458,9 @@ $(function(){ //setElementOrigin($('.ribbon-set'), 'top', 'left') // we have an image file... - if((DATA_ATTR + '_IMAGES_FILE') in localStorage){ - var loading = loadFileImages(localStorage[DATA_ATTR + '_IMAGES_FILE']) + if((DATA_ATTR + '_BASE_URL') in localStorage){ + BASE_URL = localStorage[DATA_ATTR + '_BASE_URL'] + var loading = loadFileImages() .done(function(){ var d = loadLocalStorageData() DATA = d.data diff --git a/ui/keybindings3.js b/ui/keybindings3.js index 927ba07e..52bbd488 100755 --- a/ui/keybindings3.js +++ b/ui/keybindings3.js @@ -212,6 +212,8 @@ var KEYBOARD_CONFIG = { saveLocalStorageMarks() saveLocalStorageSettings() + + saveFileState() }) }, Z: { diff --git a/ui/lib/jli.js b/ui/lib/jli.js index 8463d334..7b1e4c06 100755 --- a/ui/lib/jli.js +++ b/ui/lib/jli.js @@ -597,6 +597,34 @@ var cancelAnimationFrame = (window.cancelRequestAnimationFrame || clearTimeout) +Date.prototype.getTimeStamp = function(){ + var y = this.getFullYear() + var M = this.getMonth()+1 + M = M < 10 ? '0'+M : M + var D = this.getDate() + D = D < 10 ? '0'+D : D + var H = this.getHours() + H = H < 10 ? '0'+H : H + var m = this.getMinutes() + m = m < 10 ? '0'+m : m + + return ''+y+M+D+H+m +} +Date.prototype.setTimeStamp = function(ts){ + this.setFullYear(ts.slice(0, 4)) + this.setMonth(ts.slice(4, 6)*1-1) + this.setDate(ts.slice(6, 8)) + this.setHours(ts.slice(8, 10)) + this.setMinutes(ts.slice(10, 12)) + return this +} +Date.timeStamp = function(){ + return (new Date()).getTimeStamp() +} +Date.fromTimeStamp = function(ts){ + return (new Date()).setTimeStamp(ts) +} + /**********************************************************************