diff --git a/ui (gen4)/file.js b/ui (gen4)/file.js index 8c18681a..a6c3f34c 100755 --- a/ui (gen4)/file.js +++ b/ui (gen4)/file.js @@ -7,16 +7,21 @@ var pathlib = require('path') var events = require('events') -var fse = require('fs.extra') +var fse = require('fs-extra') var glob = require('glob') var Promise = require('promise') +// XXX seems that we need different buids of this for use with node and nw... +// XXX BUG: nw-gyp does not support msvs2015... +//var sharp = require('sharp') + var guaranteeEvents = require('guarantee-events') define(function(require){ var module = {} console.log('>>> file') + //var DEBUG = DEBUG != null ? DEBUG : true var data = require('data') @@ -73,8 +78,8 @@ module.gGlob = function(){ var listIndexes = module.listIndexes = -function(base){ - return gGlob(base +'/**/'+ INDEX_DIR) +function(base, index_dir){ + return gGlob(base +'/**/'+ index_dir || INDEX_DIR) } @@ -93,6 +98,8 @@ function listJSON(path, pattern){ var loadFile = Promise.denodeify(fse.readFile) +var writeFile = Promise.denodeify(fse.writeFile) +var ensureDir = Promise.denodeify(fse.ensureDir) // XXX handle errors... @@ -174,7 +181,8 @@ function loadJSON(path){ // ...(a) seems more logical... var loadIndex = module.loadIndex = -function(path, logger){ +function(path, index_dir, logger){ + index_dir = index_dir || INDEX_DIR // XXX should this be interactive (a-la EventEmitter) or as it is now // return the whole thing as a block (Promise)... // NOTE: one way to do this is use the logger, it will get @@ -182,7 +190,7 @@ function(path, logger){ return new Promise(function(resolve, reject){ // we've got an index... // XXX do we need to check if if it's a dir??? - if(pathlib.basename(path) == INDEX_DIR){ + if(pathlib.basename(path) == index_dir){ logger && logger.emit('path', path) @@ -355,7 +363,7 @@ function(path, logger){ var loaders = [] // XXX handle 'error' event... - listIndexes(path) + listIndexes(path, index_dir) // XXX handle errors... .on('error', function(err){ logger && logger.emit('error', err) @@ -369,7 +377,7 @@ function(path, logger){ // dir (the parent dir to the index root) // we do not need to include the index // itself in the base path... - var p = path.split(INDEX_DIR)[0] + var p = path.split(index_dir)[0] res[p] = obj[path] })) }) @@ -401,8 +409,9 @@ function(path, logger){ // XXX handle errors.... var loadPreviews = module.loadPreviews = -function(base, previews, absolute_path){ +function(base, previews, index_dir, absolute_path){ previews = previews || {} + index_dir = index_dir || INDEX_DIR return new Promise(function(resolve, reject){ listIndexes(base) @@ -438,7 +447,7 @@ function(base, previews, absolute_path){ // add a preview... // NOTE: this will overwrite a previews if they are found in // several locations... - images[gid].preview[res] = INDEX_DIR +'/'+ path.split(INDEX_DIR)[1] + images[gid].preview[res] = index_dir +'/'+ path.split(index_dir)[1] }) }) .on('end', function(){ @@ -518,8 +527,130 @@ module.buildIndex = function(index, base_path){ +/*********************************************************************/ +// Builder... +// - read list +// - generate previews (use config) +// - build images/data +// .loadURLs(..) +// - write (writer) + + + /*********************************************************************/ // Writer... +// +// This is just like the loader, done in two stages: +// - format dependent de-construction (symetric to buildIndex(..)) +// - generic writer... +// +// NOTE: for now we'll stick to the current format... + +// this will take the output of .json() +// +// .data +// .images +// .bookmarked +// .marked +// .tags +// .current +// +// NOTE: this will prepare for version 2.0 file structure... +// +// XXX write tags, marks and bookmarks only if changed... +var prepareIndex = +module.prepareIndex = +function(json, changes){ + changes = changes || {} + + var res = { + data: json.data, + current: json.data.current, + } + + // NOTE: we write the whole set ONLY if an item is true or undefined + // i.e. not false... + if(changes.bookmarked !== false){ + res.bookmarked = json.data.tags.bookmark + } + + if(changes.selected !== false){ + res.marked = json.data.tags.selected + } + + if(changes.tags !== false){ + res.tags = json.data.tags + } + + // clean out some stuff from data... + delete res.data.tags.bookmark + delete res.data.tags.selected + delete res.data.tags + + if(changes.images){ + var diff = res['images-diff'] = {} + changes.images.forEach(function(gid){ + diff[gid] = json.images[gid] + }) + + } else { + res.images = json.images + } + + return res +} + + +var FILENAME = '${DATE}-${KEYWORD}.${EXT}' + +var writeIndex = +module.writeIndex = +function(json, path, filename_tpl, logger){ + filename_tpl = filename_tpl || FILENAME + // XXX for some reason this gets the unpatched node.js Date, so we + // get the patched date explicitly... + var date = window.Date.timeStamp() + var files = [] + + // build the path if it does not exist... + return ensureDir(path) + .catch(function(err){ + logger && logger.emit('error', err) + }) + .then(function(){ + logger && logger.emit('path', path) + + // write files... + // NOTE: we are not doing this sequencilly as there will not + // be too many files... + return Promise + .all(Object.keys(json).map(function(keyword){ + var file = path +'/'+ (filename_tpl + .replace('${DATE}', date) + .replace('${KEYWORD}', keyword) + .replace('${EXT}', 'json')) + + return ensureDir(pathlib.dirname(file)) + .then(function(){ + files.push(file) + var data = JSON.stringify(json[keyword]) + + logger && logger.emit('queued', file) + + return writeFile(file, data, 'utf8') + .catch(function(err){ + logger && logger.emit('error', err) + }) + .then(function(){ + logger && logger.emit('written', file) + }) + }) + })) + .then(function(){ + logger && logger.emit('done', files) + }) + }) +} diff --git a/ui (gen4)/package.json b/ui (gen4)/package.json index 3c342cb8..5b9c7c70 100755 --- a/ui (gen4)/package.json +++ b/ui (gen4)/package.json @@ -18,10 +18,11 @@ "dependencies": { "flickrapi": "^0.3.28", "fs-walk": "0.0.1", - "fs.extra": "^1.2.1", + "fs-extra": "*", "glob": "^4.0.6", "guarantee-events": "^1.0.0", "promise": "^6.0.1", - "requirejs": "*" + "requirejs": "*", + "sharp": "^0.12.0" } } diff --git a/ui (gen4)/ui.js b/ui (gen4)/ui.js index a9aa7b6b..f06f4598 100755 --- a/ui (gen4)/ui.js +++ b/ui (gen4)/ui.js @@ -4,7 +4,6 @@ * **********************************************************************/ - window.nodejs = (typeof(process) === 'object' && process.features.uv) ? { require: window.require, diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js index 7b42c5e0..a7c11f64 100755 --- a/ui (gen4)/viewer.js +++ b/ui (gen4)/viewer.js @@ -2079,13 +2079,6 @@ module.AutoAlignRibbons = ImageGridFeatures.Feature({ }) -// XXX add a feature not to align the ribbons and focus the central -// image on next prev ribbon... -// XXX in general need a way to control .nextRibbon(..)/.prevRibbon(..) -// image selection... - -// XXX this should also define up/down navigation behavior e.g. what to -// focus on next/prev ribbon... // XXX should .alignByOrder(..) be a feature-specific action or global // as it is now??? var AlignRibbonsToImageOrder = @@ -2157,6 +2150,7 @@ module.ManualAlignRibbons = ImageGridFeatures.Feature({ //--------------------------------------------------------------------- // XXX at this point this does not support target lists... +// XXX shift up/down to new ribbon is not too correct... var ShiftAnimation = module.ShiftAnimation = ImageGridFeatures.Feature({ title: '', @@ -2166,7 +2160,9 @@ module.ShiftAnimation = ImageGridFeatures.Feature({ depends: ['ui'], handlers: [ - ['shiftImageUp.pre shiftImageDown.pre', + //['shiftImageUp.pre shiftImageDown.pre ' + // +'travelImageUp.pre travelImageDown.pre', + ['shiftImageUp.pre shiftImageDown.pre', function(target){ // XXX do not do target lists... if(target != null && target.constructor === Array @@ -3329,10 +3325,16 @@ if(window.nodejs != null){ var FileSystemLoaderActions = actions.Actions({ + config: { + 'index-dir': '.ImageGrid', + }, + // NOTE: when passed no path this will not do anything... // XXX should this set something like .path??? // ...and how should this be handled when merging indexes or // viewing multiple/clustered indexes??? + // XXX add a symmetric equivalent to .prepareIndexForWrite(..) so as + // to enable features to load their data... // XXX look inside... loadIndex: ['File/Load index', function(path, logger){ @@ -3345,7 +3347,7 @@ var FileSystemLoaderActions = actions.Actions({ // XXX get a logger... logger = logger || this.logger - file.loadIndex(path, logger) + file.loadIndex(path, this.config['index-dir'], logger) .then(function(res){ // XXX if res is empty load raw... @@ -3593,6 +3595,67 @@ module.FileSystemLoaderUI = ImageGridFeatures.Feature({ //--------------------------------------------------------------------- // fs writer... +var FileSystemWriterActions = actions.Actions({ + config: { + // XXX should this include the '.ImageGrid/' section??? + 'index-filename-template': '${DATE}-${KEYWORD}.${EXT}', + }, + + // This is here so as other features can participate in index + // preparation... + // There are several stages features can control the output format: + // 1) .json() action + // - use this for global high level serialization format + // - the output of this is .load(..) compatible + // 2) .prepareIndex(..) action + // - use this for file system write preparation + // - this directly affects the index structure + prepareIndexForWrite: ['File/Prepare index for writing', + function(json){ + return file.prepareIndex(json || this.json()) + }], + // XXX should this get the base uncropped state or the current state??? + // XXX get real base path... + saveIndex: ['File/save index', + function(path, logger){ + // XXX get real base path... + path = path || this.base_path +'/'+ this.config['index-dir'] + + file.writeIndex( + this.prepareIndexForWrite(), + path, + this.config['index-filename-template'], + logger || this.logger) + }], + + // XXX same as ctrl-shif-s in gen3 + exportView: ['File/Export current view', + function(){ + }], + // XXX export current state as a full loadable index + // XXX might be interresting to unify this and .exportView(..) + exportCollection: ['File/Export as collection', + function(){ + }], +}) + + +var FileSystemWriter = +module.FileSystemWriter = ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'fs-writer', + // NOTE: this is mostly because of the base path handling... + depends: ['fs-loader'], + + actions: FileSystemWriterActions, + + isApplicable: function(){ + return window.nodejs != null + }, +}) + //--------------------------------------------------------------------- @@ -3623,6 +3686,7 @@ ImageGridFeatures.Feature('viewer-testing', [ 'fs-loader', 'fs-loader-ui', + 'fs-writer', 'app-control',