/********************************************************************** * * * **********************************************************************/ ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ var util = require('lib/util') var object = require('lib/object') var actions = require('lib/actions') var features = require('lib/features') var core = require('features/core') /*********************************************************************/ // XXX experimental... // XXX need the other .location stuff to be visible/accessible... // ...now this only shows path... var LocationProto = { get path(){ return this.__actions.__location.path }, set path(value){ this.__actions.location = value }, __init__: function(actions){ this.__actions = actions // XXX this does not work... // ...the oother way around seems best: // actions.__location.__proto__ = this //this.__proto__ = actions.__location }, } var Location = object.Constructor('Location', LocationProto) /*********************************************************************/ // XXX add url scheme support... // ://#? // XXX add .hash support for in-location .current setting when no index // available... // XXX this should provide mechaincs to define location handlers, i.e. // a set for loader/saver per location type (.method) // XXX revise the wording... // .path or .url var LocationActions = actions.Actions({ config: { 'default-load-method': null, }, // Format: // { // path: , // method: , // current: , // ... // } // // NOTE: these will remove the trailing '/' (or '\') from .path // unless the path is root (i.e. "/")... // ...this is mainly to facilitate better browse support, i.e. // to open the dir (open parent + select current) and not // within the dir __location: null, get location(){ this.__location = this.__location || {} var b = this.__location.path if(b && b != '/' && b != '\\'){ b = util.normalizePath(b) } if(b){ this.__location.path = b } this.__location.current = this.current return this.__location //return Location(this) }, // NOTE: this is a shorthand for .loadLocation(..) // NOTE: the method is needed to enable us to get the action return // value... set location(value){ this.loadLocation(value) }, clearLoaction: ['File/Clear location', function(){ delete this.__location }], // Load location... // // Reload current location... // .loadLocation() // -> result // // Load new path using current location method and data... // .loadLocation(path) // -> result // // Load new location... // .loadLocation(location) // -> result // NOTE: this is almost the same as .location = location but // here we can access the call return value. // // NOTE: .location will be set by the .load handler... // // XXX not sure about where to set the .__location -- see inside... loadLocation: ['- File/Load location', function(location){ location = location || this.location // got a path -> load using current location data... if(typeof(location) == typeof('str')){ location = { path: path, method: (this.__location && this.__location.method) || this.config['default-load-method'], current: this.current, } // got an object... } else { // clone the location... location = JSON.parse(JSON.stringify(location)) } var method = location.method || this.location.method || this.config['default-load-method'] var cur = location.current var path = location.path // normalize path if it's not root... if(path != '/' && path != '\\'){ path = location.path = util.normalizePath(path) } // XXX ??? //this.__location = location // NOTE: the method should set the proper location if it uses .clear()... var res = method && this[method](path) // load current... if(cur){ if(res && res.then != null){ var that = this res.then(function(){ that.current = cur }) } else { this.current = cur } } return res }], // XXX // XXX should these have the same effect as .dispatch('location:*:load', location)??? // ...another way to put it is should we call this from dispatch? // ...feels like yes -- this will turn into another Action-like // protocol... // ...or we can use this to wrap the actual matching action... _loadLocation: ['- File/Save location', {protocol: 'location:*:load'}, function(location){ this.location.method = this.locationMethod(location) this.dispatch('location:*:load', location) }], _saveLocation: ['- File/Save location', {protocol: 'location:*:save'}, function(location){ this.location.method = this.locationMethod(location) this.dispatch('location:*:save', location) }], _locationMethod: ['- File/', {protocol: 'location:?'}, function(location){ return (location || this.location).method || null }], // format: // { // 'protocol:method': 'actionName', // // 'family:protocol:method': 'actionName', // // 'family:*': 'actionName', // ... // } get protocols(){ var cache = this.__location_protocol_cache = this.__location_protocol_cache || this.cacheProtocols() return cache }, cacheProtocols: ['- File/', function(){ var that = this var res = {} this.actions.forEach(function(n){ var proto = that.getActionAttr(n, 'protocol') if(proto){ res[proto] = n } }) return res }], // XXX how do we call the dispatched actions and all the matching // pattern actions??? // One way to go would be: // .dispatch('location:*:save', ..) // -> 'location:?' - get the default for '*' // -> 'location:*:save' (pre) // -> 'location:file:save' - forms the return value // -> 'location:*:save' (post) // // ...this should the same as calling (???): // .loadLocation(..) // XXX sanity check: how is this different for what Action(..) does??? // ...the only thing this adds is a way not to call some of the // overloading actions via a simple pattern matching mechanism... // Example: // .dispatch('location:file:save', ..) // -> calls only the "save" actions that match the // location:file protocol ignoring all other // implementations... // ...for pure actions this is also possible by manually checking // some condition and doing nothing if not matched... dispatch: ['- File/', core.doc` Execute command in specific protocol... .dispatch('protocol:command', ..) .dispatch('family:protocol:command', ..) -> result XXX defaults... XXX introspection... `, function(spec){ var args = [...arguments].slice(1) spec = spec instanceof Array ? spec : spec.split(':') var cache = this.protocols var protocols = Object.keys(cache) // get all matching paths... var matches = protocols.slice() .map(function(p){ return p.split(':') }) spec.forEach(function(e, i){ matches = matches .filter(function(p){ return e == '*' || p[i] == e }) }) matches = matches // remove matches ending with '*'... (XXX ???) .filter(function(p){ return p.slice(-1)[0] != '*' }) .map(function(p){ return p.join(':') }) // fill in the gaps... var i = spec.indexOf('*') while(spec.indexOf('*') >= 0){ var handler = cache[spec.slice(0, i).concat('?').join(':')] if(handler){ spec[i] = this[handler].apply(this, args) i = spec.indexOf('*') // error... // XXX how do we break out of this??? } else { throw ('No default defined for: '+ spec.slice(0, i+1).join(':')) } } // introspection... // XXX this supports only one '??' var i = spec.indexOf('??') if(i >= 0){ var head = spec.slice(0, i).join(':') var tail = spec.slice(i+1).join(':') console.log(head, tail) return protocols .filter(function(p){ return p.startsWith(head) && (tail == '' || (p.endsWith(tail) && p.length > (head.length + tail.length + 2))) }) // call method... } else { var m = spec.join(':') console.log('>>>', m) // XXX take all the matches and chain call them... return this[cache[m]].apply(this, args) } }], // 1) store .location // 2) cleanup .images[..].base_path // // XXX might be good to make the .base_path relative to location // if possible... // XXX not sure if this is the right place for .images[..].base_path // handling... json: [function(){ return function(res){ if(this.location){ var l = res.location = JSON.parse(JSON.stringify(this.location)) // cleanup base_path... Object.keys(res.images || {}).forEach(function(gid){ var img = res.images[gid] if(l.path == img.base_path){ delete img.base_path } }) } }}], load: [function(){ return function(_, data){ var that = this // NOTE: we are setting this after the load because the // loader may .clear() the viewer, thus clearing the // .location too... var l = this.__location = data.location // set default image .base_path // XXX not sure we need this... data.location && Object.keys(this.images || {}).forEach(function(gid){ var img = that.images[gid] if(img.base_path == null){ img.base_path = l.path } }) }}], clone: [function(){ return function(res){ if(this.location){ res.__location = JSON.parse(JSON.stringify(this.__location)) } }}], clear: [function(){ this.clearLoaction() }], }) module.Location = core.ImageGridFeatures.Feature({ title: '', doc: '', tag: 'location', depends: [ 'base', ], actions: LocationActions, handlers: [ ], }) /********************************************************************** * vim:set ts=4 sw=4 : */ return module })