mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
577 lines
14 KiB
JavaScript
Executable File
577 lines
14 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
* Core features that setup the life-cycle and the base interfaces for
|
|
* features to use...
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
|
/*********************************************************************/
|
|
|
|
// XXX
|
|
var DEBUG = typeof(DEBUG) != 'undefined' ? DEBUG : true
|
|
|
|
var util = require('lib/util')
|
|
var object = require('lib/object')
|
|
var actions = require('lib/actions')
|
|
var features = require('lib/features')
|
|
var toggler = require('lib/toggler')
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// Make a protocol implementation action...
|
|
//
|
|
// For more docs see: docs for actions.js and .chainApply(..)
|
|
//
|
|
// XXX might be good to move this to actions.js
|
|
// XXX might also be a good idea to mark the actual protocol definition
|
|
// and not just the implementation...
|
|
var protocol =
|
|
module.protocol = function(protocol, func){
|
|
return function(){
|
|
return this[protocol].chainApply(this, func, arguments)
|
|
}
|
|
}
|
|
|
|
|
|
// NOTE: if no toggler state is set this assumes that the first state
|
|
// is the default...
|
|
var makeConfigToggler =
|
|
module.makeConfigToggler =
|
|
function(attr, states, a, b){
|
|
|
|
var pre = a
|
|
// XXX is this a good default???
|
|
//var post = b || function(action){ action != null && this.focusImage() }
|
|
var post = b
|
|
|
|
return toggler.Toggler(null,
|
|
function(_, action){
|
|
var lst = states.constructor === Array ? states : states.call(this)
|
|
|
|
if(action == null){
|
|
return this.config[attr] || lst[lst.indexOf('none')] || lst[0]
|
|
|
|
} else {
|
|
this.config[attr] = action
|
|
}
|
|
},
|
|
states, pre, post)
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// Root ImageGrid.viewer object constructor...
|
|
//
|
|
// This adds:
|
|
// - toggler as action compatibility
|
|
//
|
|
var ImageGridMetaActions =
|
|
module.ImageGridMetaActions = {
|
|
// Test if the action is a Toggler...
|
|
//
|
|
isToggler: actions.doWithRootAction(function(action){
|
|
return action instanceof toggler.Toggler }),
|
|
|
|
// Handle special cases where we need to get the action result early,
|
|
// without calling handlers...
|
|
//
|
|
// These include:
|
|
// - toggler action special command handling (like: '?', '??', ..)
|
|
//
|
|
preActionHandler: actions.doWithRootAction(function(action, name, handlers, args){
|
|
// Special case: do not call handlers for toggler state queries...
|
|
//
|
|
// NOTE: if the root handler is instance of Toggler (jli) and
|
|
// the action is called with '?'/'??' as argument, then the
|
|
// toggler will be called with the argument and return the
|
|
// result bypassing the handlers.
|
|
// NOTE: an action is considered a toggler only if it's base action
|
|
// is a toggler (instance of Toggler), thus, the same "top"
|
|
// action can be or not be a toggler in different contexts.
|
|
//
|
|
// For more info on togglers see: lib/toggler.js
|
|
if(this.isToggler(name)
|
|
&& args.length == 1
|
|
&& (args[0] == '?' || args[0] == '??')){
|
|
return {
|
|
result: handlers.slice(-1)[0].pre.apply(this, args),
|
|
}
|
|
}
|
|
}),
|
|
}
|
|
ImageGridMetaActions.__proto__ = actions.MetaActions
|
|
|
|
var ImageGrid =
|
|
module.ImageGrid =
|
|
object.makeConstructor('ImageGrid', ImageGridMetaActions)
|
|
|
|
// Root ImageGrid feature set....
|
|
var ImageGridFeatures =
|
|
module.ImageGridFeatures = new features.FeatureSet()
|
|
|
|
// setup base instance constructor...
|
|
ImageGridFeatures.__actions__ =
|
|
function(){ return actions.Actions(ImageGrid()) }
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// Setup runtime info...
|
|
|
|
// nw or node...
|
|
if(typeof(process) != 'undefined'){
|
|
|
|
// nw.js 0.13+
|
|
if(typeof(nw) != 'undefined'){
|
|
ImageGridFeatures.runtime = 'nw'
|
|
|
|
// NOTE: jli is patching the Date object and with two separate
|
|
// instances we'll need to sync things up...
|
|
// XXX HACK: if node and chrome Date implementations ever
|
|
// significantly diverge this will break things + this is
|
|
// a potential data leak between contexts...
|
|
//global.Date = window.Date
|
|
|
|
// XXX this is less of a hack but it is still an implicit
|
|
util.patchDate(global.Date)
|
|
util.patchDate(window.Date)
|
|
|
|
// node...
|
|
} else {
|
|
ImageGridFeatures.runtime = 'node'
|
|
|
|
// XXX patch Date...
|
|
// XXX this will not work directly as we will need to explicitly
|
|
// require jli...
|
|
//patchDate(global.Date)
|
|
}
|
|
|
|
// browser...
|
|
// NOTE: we're avoiding detecting browser specifics for as long as possible,
|
|
// this will minimize the headaches of supporting several non-standard
|
|
// versions of code...
|
|
} else if(typeof(window) != 'undefined'){
|
|
ImageGridFeatures.runtime = 'browser'
|
|
|
|
// unknown...
|
|
// XXX do we need to detect chrome app???
|
|
} else {
|
|
ImageGridFeatures.runtime = 'unknown'
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
// System life-cycle...
|
|
|
|
// XXX should this be a generic library thing???
|
|
// XXX should his have state???
|
|
// ...if so, should this be a toggler???
|
|
var LifeCycleActions = actions.Actions({
|
|
start: ['- System/',
|
|
function(){
|
|
var that = this
|
|
this.logger && this.logger.emit('start')
|
|
|
|
// NOTE: jQuery currently provides no way to check if an event
|
|
// is bound so we'll need to keep track manually...
|
|
if(this.__stop_handler == null){
|
|
var stop = this.__stop_handler = function(){ that.stop() }
|
|
|
|
} else {
|
|
return
|
|
}
|
|
|
|
// set the runtime...
|
|
var runtime = this.runtime = ImageGridFeatures.runtime
|
|
|
|
// nw.js...
|
|
if(runtime == 'nw'){
|
|
// this handles both reload and close...
|
|
$(window).on('beforeunload', stop)
|
|
|
|
// NOTE: we are using both events as some of them do not
|
|
// get triggered in specific conditions and some do,
|
|
// for example, this gets triggered when the window's
|
|
// 'X' is clicked while does not on reload...
|
|
this.__nw_stop_handler = function(){
|
|
var w = this
|
|
try{
|
|
that
|
|
// wait till ALL the handlers finish before
|
|
// exiting...
|
|
.on('stop.post', function(){
|
|
// XXX might be broken in nw13 -- test!!!
|
|
//w.close(true)
|
|
nw.App.quit()
|
|
})
|
|
.stop()
|
|
|
|
// in case something breaks exit...
|
|
// XXX not sure if this is correct...
|
|
} catch(e){
|
|
console.log('ERROR:', e)
|
|
|
|
DEBUG || nw.App.quit()
|
|
//this.close(true)
|
|
}
|
|
}
|
|
nw.Window.get().on('close', this.__nw_stop_handler)
|
|
|
|
// node.js...
|
|
} else if(runtime == 'node'){
|
|
process.on('exit', stop)
|
|
|
|
// browser...
|
|
} else if(runtime == 'browser'){
|
|
$(window).on('beforeunload', stop)
|
|
|
|
// other...
|
|
} else {
|
|
// XXX
|
|
console.warn('Unknown runtime:', runtime)
|
|
}
|
|
}],
|
|
// unbind events...
|
|
stop: ['- System/',
|
|
function(){
|
|
// browser & nw...
|
|
if(this.__stop_handler
|
|
&& (this.runtime == 'browser' || this.runtime == 'nw')){
|
|
$(window).off('beforeunload', this.__stop_handler)
|
|
}
|
|
|
|
// nw...
|
|
if(this.__nw_stop_handler && this.runtime == 'nw'){
|
|
nw.Window.get().removeAllListeners('close')
|
|
delete this.__nw_stop_handler
|
|
}
|
|
|
|
// node...
|
|
if(this.__stop_handler && this.runtime == 'node'){
|
|
process.removeAllListeners('exit')
|
|
}
|
|
|
|
delete this.__stop_handler
|
|
|
|
this.logger && this.logger.emit('stop')
|
|
}],
|
|
|
|
/*
|
|
// XXX need a clear protocol for this...
|
|
// something like:
|
|
// - clear state
|
|
// - load state
|
|
reset: ['System/',
|
|
function(){
|
|
}],
|
|
*/
|
|
})
|
|
|
|
var LifeCycle =
|
|
module.LifeCycle = ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'lifecycle',
|
|
priority: 'high',
|
|
|
|
actions: LifeCycleActions,
|
|
})
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// Introspection...
|
|
|
|
// Indicate that an action is not intended for direct use...
|
|
//
|
|
// NOTE: this will not do anything but mark the action.
|
|
var notUserCallable =
|
|
module.notUserCallable = function(func){
|
|
func.__not_user_callable__ = true
|
|
return func
|
|
}
|
|
|
|
|
|
var IntrospectionActions = actions.Actions({
|
|
// user-callable actions...
|
|
get useractions(){
|
|
return this.actions.filter(this.isUserCallable.bind(this)) },
|
|
|
|
// check if action is callable by user...
|
|
isUserCallable: ['- System/',
|
|
actions.doWithRootAction(function(action){
|
|
return action.__not_user_callable__ != true })],
|
|
})
|
|
|
|
|
|
var Introspection =
|
|
module.Introspection = ImageGridFeatures.Feature({
|
|
title: '',
|
|
|
|
tag: 'introspection',
|
|
|
|
actions: IntrospectionActions,
|
|
})
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// Workspace...
|
|
//
|
|
// Basic protocol:
|
|
// A participating feature should:
|
|
// - react to .saveWorkspace(..) by saving it's relevant state data to the
|
|
// object returned by the .saveWorkspace() action.
|
|
// NOTE: it is recommended that a feature save its relevant .config
|
|
// data as-is.
|
|
// NOTE: no other action or state change should be triggered by this.
|
|
// - react to .loadWorkspace(..) by loading it's state from the returned
|
|
// object...
|
|
// NOTE: this can be active, i.e. a feature may call actions when
|
|
// handling this.
|
|
// - react to .toggleChrome(..) and switch on and off the chrome
|
|
// visibility... (XXX)
|
|
//
|
|
//
|
|
|
|
|
|
// Helpers...
|
|
var makeWorkspaceConfigWriter =
|
|
module.makeWorkspaceConfigWriter = function(keys, callback){
|
|
return function(workspace){
|
|
var that = this
|
|
|
|
keys = typeof(keys) == typeof(function(){}) ? keys.call(this) : keys
|
|
|
|
// store data...
|
|
keys.forEach(function(key){
|
|
workspace[key] = JSON.parse(JSON.stringify(that.config[key]))
|
|
})
|
|
|
|
callback && callback.call(this, workspace)
|
|
}
|
|
}
|
|
|
|
// XXX should this delete a prop if it's not in the loading workspace???
|
|
// XXX only replace a prop if it has changed???
|
|
var makeWorkspaceConfigLoader =
|
|
module.makeWorkspaceConfigLoader = function(keys, callback){
|
|
return function(workspace){
|
|
var that = this
|
|
|
|
keys = typeof(keys) == typeof(function(){}) ? keys.call(this) : keys
|
|
|
|
// load data...
|
|
keys.forEach(function(key){
|
|
// the key exists...
|
|
if(key in workspace){
|
|
that.config[key] = JSON.parse(JSON.stringify(workspace[key]))
|
|
|
|
// no key set...
|
|
// XXX is this the right way to go???
|
|
} else {
|
|
delete that.config[key]
|
|
}
|
|
})
|
|
|
|
callback && callback.call(this, workspace)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var WorkspaceActions = actions.Actions({
|
|
config: {
|
|
'load-workspace': 'default',
|
|
|
|
'workspace': 'default',
|
|
'workspaces': {},
|
|
},
|
|
|
|
get workspace(){
|
|
return this.config.workspace
|
|
},
|
|
set workspace(value){
|
|
this.loadWorkspace(value)
|
|
},
|
|
|
|
get workspaces(){
|
|
return this.config.workspaces
|
|
},
|
|
|
|
|
|
getWorkspace: ['- Workspace/',
|
|
function(){ return this.saveWorkspace(null) }],
|
|
|
|
// NOTE: these are mainly triggers for other features to save/load
|
|
// their specific states...
|
|
// NOTE: handlers should only set data on the workspace object passively,
|
|
// no activity is recommended.
|
|
// NOTE: if null is passed this will only get the data, but will
|
|
// save nothing. this us useful for introspection and temporary
|
|
// context storage.
|
|
//
|
|
// XXX for some reason this does not get saved with .config...
|
|
saveWorkspace: ['Workspace/Save Workspace',
|
|
function(name){
|
|
if(!this.config.hasOwnProperty('workspaces')){
|
|
this.config['workspaces'] = JSON.parse(JSON.stringify(this.config['workspaces']))
|
|
}
|
|
|
|
var res = {}
|
|
|
|
if(name !== null){
|
|
this.config['workspaces'][name || this.config.workspace] = res
|
|
}
|
|
|
|
return res
|
|
}],
|
|
// NOTE: merging the state data is the responsibility of the feature
|
|
// ...this is done so as not to restrict the feature to one
|
|
// specific way to do stuff...
|
|
loadWorkspace: ['Workspace/Load Workspace',
|
|
function(name){
|
|
name = name || this.config.workspace
|
|
|
|
// get a workspace by name and load it...
|
|
if(typeof(name) == typeof('str')){
|
|
this.config.workspace = name
|
|
|
|
return this.workspaces[name] || {}
|
|
|
|
// we got the workspace object...
|
|
} else {
|
|
return name
|
|
}
|
|
}],
|
|
|
|
// NOTE: this will not save the current workspace...
|
|
toggleWorkspace: ['Workspace/Toggle Workspace',
|
|
makeConfigToggler('workspace',
|
|
function(){ return Object.keys(this.config['workspaces']) },
|
|
function(state){ this.loadWorkspace(state) })],
|
|
|
|
// XXX should we keep the stack unique???
|
|
pushWorkspace: ['- Workspace/',
|
|
function(name){
|
|
name = name || this.workspace
|
|
var stack = this.__workspace_stack = this.__workspace_stack || []
|
|
|
|
this.saveWorkspace()
|
|
|
|
if(stack.slice(-1)[0] == name){
|
|
return
|
|
}
|
|
|
|
this.workspace != name && this.loadWorkspace(name)
|
|
stack.push(name)
|
|
}],
|
|
popWorkspace: ['- Workspace/',
|
|
function(){
|
|
var stack = this.__workspace_stack
|
|
|
|
if(!stack || stack.length == 0){
|
|
return
|
|
}
|
|
|
|
this.saveWorkspace()
|
|
this.loadWorkspace(stack.pop())
|
|
}],
|
|
})
|
|
|
|
|
|
var Workspace =
|
|
module.Workspace = ImageGridFeatures.Feature({
|
|
title: '',
|
|
|
|
tag: 'workspace',
|
|
|
|
depends: [
|
|
'lifecycle',
|
|
],
|
|
|
|
actions: WorkspaceActions,
|
|
|
|
handlers: [
|
|
['start',
|
|
function(){
|
|
this.loadWorkspace(this.config['load-workspace'] || 'default') }],
|
|
['stop',
|
|
function(){
|
|
this.saveWorkspace() }],
|
|
],
|
|
})
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
// Tasks...
|
|
// XXX should this be a separate module???
|
|
|
|
var tasks = require('lib/tasks')
|
|
|
|
// XXX see if a protocol can be practical here to:
|
|
// - serialize/restore jobs
|
|
// - ...
|
|
var TaskActions = actions.Actions({
|
|
config: {
|
|
},
|
|
|
|
get jobs(){
|
|
return this.__jobs
|
|
},
|
|
|
|
getJob: ['- Jobs/',
|
|
function(name){
|
|
name = name || this.data.newGid()
|
|
|
|
// get/init task dict...
|
|
var t = this.__jobs = this.__jobs || {}
|
|
// get/init task...
|
|
var job = t[name] = t[name] || tasks.Queue()
|
|
job.name = name
|
|
|
|
return job
|
|
}],
|
|
|
|
// XXX stop
|
|
})
|
|
|
|
|
|
var Tasks =
|
|
module.Tasks = ImageGridFeatures.Feature({
|
|
title: '',
|
|
|
|
tag: 'tasks',
|
|
|
|
depends: [ ],
|
|
|
|
actions: TaskActions,
|
|
|
|
handlers: [
|
|
['start',
|
|
function(){
|
|
// XXX prepare for recovery and recover...
|
|
}],
|
|
['stop',
|
|
function(){
|
|
// XXX stop tasks and prepare for recovery...
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */ return module })
|