mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
810 lines
20 KiB
JavaScript
Executable File
810 lines
20 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
|
/*********************************************************************/
|
|
|
|
var actions = require('lib/actions')
|
|
var features = require('lib/features')
|
|
var toggler = require('lib/toggler')
|
|
|
|
var core = require('features/core')
|
|
var widgets = require('features/ui-widgets')
|
|
|
|
var overlay = require('lib/widget/overlay')
|
|
var browse = require('lib/widget/browse')
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
// url history...
|
|
|
|
var URLHistoryActions = actions.Actions({
|
|
config: {
|
|
'url-history-push-up-on-open': false,
|
|
|
|
// values:
|
|
// -1 - no limit.
|
|
// 0 - disabled
|
|
// 1+ - length of history
|
|
'url-history-length': 100,
|
|
},
|
|
|
|
__url_history: null,
|
|
|
|
// Format:
|
|
// {
|
|
// url: {
|
|
// open: <action-name> | <function>,
|
|
// check: <action-name> | <function>,
|
|
// },
|
|
// ...
|
|
// }
|
|
//
|
|
// NOTE: last opened url is last...
|
|
// NOTE: though functions are supported they are not recommended as
|
|
// we can not stringify them to JSON...
|
|
get url_history(){
|
|
return this.hasOwnProperty('__url_history') ? this.__url_history : undefined },
|
|
set url_history(value){
|
|
this.__url_history = value },
|
|
|
|
|
|
clone: [function(full){
|
|
return function(res){
|
|
res.url_history = null
|
|
if(full && this.url_history){
|
|
res.url_history = JSON.parse(JSON.stringify(this.url_history))
|
|
}
|
|
}
|
|
}],
|
|
|
|
setTopURLHistory: ['- History/',
|
|
function(url){
|
|
var data = this.url_history[url]
|
|
|
|
if(data == null){
|
|
return
|
|
}
|
|
|
|
delete this.url_history[url]
|
|
this.url_history[url] = data
|
|
}],
|
|
// NOTE: if clear is not true then this will update a history item
|
|
// rather than fully rewriting it...
|
|
pushURLToHistory: ['- History/',
|
|
function(url, open, check, clear){
|
|
url = url || this.location.path
|
|
var l = this.config['url-history-length'] || -1
|
|
|
|
if(l == 0){
|
|
return
|
|
}
|
|
|
|
this.url_history = this.url_history || {}
|
|
var item = !clear ? (this.url_history[url] || {}) : {}
|
|
|
|
open = item.open = open || this.location.method
|
|
check = item.check = check || 'checkPath'
|
|
|
|
// remove the old value...
|
|
if(url in this.url_history && this.config['url-history-push-up-on-open']){
|
|
delete this.url_history[url]
|
|
}
|
|
|
|
// push url to history...
|
|
this.url_history[url] = item
|
|
/*this.url_history[url] = {
|
|
open: open,
|
|
check: check,
|
|
}*/
|
|
|
|
// update history length...
|
|
if(l > 0){
|
|
var k = Object.keys(this.url_history)
|
|
while(k.length > l){
|
|
// drop first url in order -- last added...
|
|
this.dropURLFromHistory(k[0])
|
|
var k = Object.keys(this.url_history)
|
|
}
|
|
}
|
|
}],
|
|
// NOTE: url can be an index, 0 being the last url added to history;
|
|
// negative values are also supported.
|
|
dropURLFromHistory: ['- History/',
|
|
function(url){
|
|
this.url_history = this.url_history || {}
|
|
|
|
url = typeof(url) == typeof(123) ?
|
|
Object.keys(this.url_history).reverse().slice(url)[0]
|
|
: url
|
|
|
|
if(url){
|
|
delete this.url_history[url]
|
|
}
|
|
}],
|
|
checkURLFromHistory: ['- History/',
|
|
function(url){
|
|
this.url_history = this.url_history || {}
|
|
|
|
url = typeof(url) == typeof(123) ?
|
|
Object.keys(this.url_history).reverse().slice(url)[0]
|
|
: url
|
|
|
|
// if we have a check action then use it...
|
|
if(url && this.url_history[url] && this.url_history[url].check){
|
|
var check = this.url_history[url].check
|
|
|
|
if(typeof(check) == typeof('str')){
|
|
return this[check](url)
|
|
|
|
} else {
|
|
return check(url)
|
|
}
|
|
|
|
// no way to check so we do not know...
|
|
} else {
|
|
return null
|
|
}
|
|
}],
|
|
toggleURLPinned: ['History/',
|
|
toggler.Toggler(
|
|
function(){ return this.location.path },
|
|
function(url, action){
|
|
var e = this.url_history[url]
|
|
|
|
// get state...
|
|
if(action == null){
|
|
return e && e.pinned ? 'on' : 'off'
|
|
|
|
// change state -> 'on'...
|
|
} else if(action == 'on'){
|
|
e.pinned = true
|
|
|
|
// change state -> 'off'...
|
|
} else if(action == 'off'){
|
|
delete e.pinned
|
|
}
|
|
},
|
|
['off', 'on'])],
|
|
openURLFromHistory: ['- History/',
|
|
function(url, open){
|
|
this.url_history = this.url_history || {}
|
|
|
|
url = typeof(url) == typeof(123) ?
|
|
Object.keys(this.url_history).reverse().slice(url)[0]
|
|
: url
|
|
|
|
if(url && !open && this.url_history[url] && this.url_history[url].open){
|
|
open = this.url_history[url].open
|
|
}
|
|
|
|
if(url && open){
|
|
if(open instanceof Function){
|
|
return open(url)
|
|
|
|
} else {
|
|
return this[open](url)
|
|
}
|
|
}
|
|
}],
|
|
clearURLHistory: ['History/',
|
|
function(){ this.url_history = null }],
|
|
})
|
|
|
|
|
|
var URLHistory =
|
|
module.URLHistory = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'url-history',
|
|
depends: [
|
|
'location',
|
|
],
|
|
suggested: [
|
|
'ui-url-history',
|
|
'url-history-local-storage',
|
|
'url-history-fs-writer',
|
|
],
|
|
|
|
actions: URLHistoryActions,
|
|
})
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
// XXX should this be responsible for saving and loading of .location???
|
|
// ...on one hand it's part of the history, on the other it's part
|
|
// of file loader...
|
|
var URLHistoryLocalStorageActions = actions.Actions({
|
|
config: {
|
|
'url-history-local-storage-key': 'url-history',
|
|
'url-history-loaded-local-storage-key': 'url-history-loaded',
|
|
'url-history-load-current': true,
|
|
},
|
|
|
|
__url_history: null,
|
|
|
|
// load url history...
|
|
get url_history(){
|
|
// get the attr value...
|
|
if(this.hasOwnProperty('__url_history') && this.__url_history){
|
|
return this.__url_history
|
|
}
|
|
|
|
var key = this.config['url-history-local-storage-key']
|
|
if(key){
|
|
// get the storage value...
|
|
// if not local __url_history and we are configured, load from storage...
|
|
if(this.config && key){
|
|
var history = localStorage[key]
|
|
if(history){
|
|
try{
|
|
this.__url_history = JSON.parse(history)
|
|
|
|
} catch(e) {
|
|
delete localStorage[key]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.hasOwnProperty('__url_history') ? this.__url_history : null
|
|
},
|
|
set url_history(value){
|
|
this.__url_history = value
|
|
|
|
var key = this.config['url-history-local-storage-key']
|
|
if(key){
|
|
localStorage[key] = JSON.stringify(value)
|
|
}
|
|
},
|
|
|
|
|
|
// Disable localStorage in child...
|
|
clone: [function(){
|
|
return function(res){
|
|
res.config['url-history-local-storage-key'] = null
|
|
res.config['url-history-loaded-local-storage-key'] = null
|
|
}
|
|
}],
|
|
|
|
saveURLHistory: ['History/',
|
|
function(){
|
|
var history = this.config['url-history-local-storage-key']
|
|
if(history != null){
|
|
localStorage[history] =
|
|
JSON.stringify(this.url_history)
|
|
}
|
|
|
|
this.saveLocation()
|
|
}],
|
|
saveLocation: ['History/',
|
|
function(){
|
|
var loaded = this.config['url-history-loaded-local-storage-key']
|
|
|
|
if(loaded != null){
|
|
localStorage[loaded] = JSON.stringify(this.location || {})
|
|
}
|
|
}],
|
|
loadLastSavedBasePath: ['- History/',
|
|
function(){
|
|
var loaded = this.config['url-history-loaded-local-storage-key']
|
|
|
|
if(loaded && localStorage[loaded]){
|
|
var l = JSON.parse(localStorage[loaded])
|
|
|
|
if(l.current != null && this.config['url-history-load-current']){
|
|
this.one('load', function(){
|
|
this.current = l.current
|
|
})
|
|
}
|
|
|
|
this.openURLFromHistory(l.path, l.method)
|
|
|
|
} else {
|
|
this.openURLFromHistory(0)
|
|
}
|
|
}]
|
|
})
|
|
|
|
var URLHistoryLocalStorage =
|
|
module.URLHistoryLocalStorage = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'url-history-local-storage',
|
|
depends: [
|
|
'ui',
|
|
'url-history',
|
|
],
|
|
|
|
isApplicable: function(){
|
|
return typeof(localStorage) != 'undefined'
|
|
&& localStorage != null },
|
|
|
|
actions: URLHistoryLocalStorageActions,
|
|
|
|
// NOTE: loading is done by the .url_history prop...
|
|
handlers: [
|
|
['ready',
|
|
function(){ this.loadLastSavedBasePath() }],
|
|
|
|
['stop.pre',
|
|
function(){ this.saveURLHistory() }],
|
|
|
|
// save base_path...
|
|
['load',
|
|
function(){ this.location && this.location.path && this.saveLocation() }],
|
|
|
|
// save...
|
|
['pushURLToHistory dropURLFromHistory setTopURLHistory',
|
|
function(){
|
|
this.saveURLHistory()
|
|
}],
|
|
// clear...
|
|
['clearURLHistory.pre',
|
|
function(){
|
|
delete this.__url_history
|
|
|
|
var history = this.config['url-history-local-storage-key']
|
|
if(history){
|
|
delete localStorage[history]
|
|
}
|
|
|
|
var loaded = this.config['url-history-loaded-local-storage-key']
|
|
if(loaded){
|
|
delete localStorage[loaded]
|
|
}
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
// XXX
|
|
var URLHistoryFSWriter =
|
|
module.URLHistoryFSWriter = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'url-history-fs-writer',
|
|
depends: [
|
|
'fs-writer',
|
|
'url-history-local-storage',
|
|
],
|
|
|
|
config: {
|
|
// XXX should we add a toggler action to toggle this?
|
|
'url-history-push-to-top-on-save': false,
|
|
},
|
|
|
|
handlers: [
|
|
['saveIndex',
|
|
function(res){
|
|
var that = this
|
|
res.then(function(l){
|
|
// push saved to top...
|
|
if(that.config['url-history-push-to-top-on-save']){
|
|
that.pushURLToHistory(l.path, l.method)
|
|
|
|
// update...
|
|
} else {
|
|
var e = that.url_history[l.path]
|
|
if(e != null){
|
|
e.open = l.method
|
|
that.saveURLHistory()
|
|
|
|
} else {
|
|
that.pushURLToHistory(l.path, l.method)
|
|
}
|
|
}
|
|
})
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
var URLHistoryUIActions = actions.Actions({
|
|
config: {
|
|
// Indicate when to remove striked items from url history list
|
|
//
|
|
// Supported values:
|
|
// - true | undefined - always remove
|
|
// - flase - never remove
|
|
// - [ 'open', 'close' ] - explicitly select event
|
|
//
|
|
// XXX do we need this???
|
|
'url-history-list-clear': ['open', 'close'],
|
|
|
|
// If true pushing the pin item button will also focus the item
|
|
//
|
|
// NOTE: Both settings have their pluses and minuses:
|
|
// enabled (true)
|
|
// + will keep the item on screen
|
|
// - will lose context
|
|
// disabled (false)
|
|
// + will keep context
|
|
// - will lose the item from view if list is long
|
|
'url-history-focus-on-pin': false,
|
|
},
|
|
|
|
// XXX use svg icons for buttons...
|
|
listURLHistory: ['History|File/Location history...',
|
|
widgets.makeUIDialog(function(){
|
|
var that = this
|
|
var parent = this.preventClosing ? this.preventClosing() : null
|
|
var cur = this.location.path
|
|
|
|
// caches...
|
|
var fs_state = {}
|
|
var to_remove = []
|
|
|
|
// remove stirked out elements...
|
|
var removeStriked = function(evt){
|
|
var rem = that.config['url-history-list-clear']
|
|
if(rem == false || rem != null && rem.indexOf(evt) < 0){
|
|
return
|
|
}
|
|
to_remove.forEach(function(e){
|
|
that.dropURLFromHistory(e)
|
|
})
|
|
to_remove = []
|
|
}
|
|
var makeHistoryList = function(fs_state){
|
|
fs_state = fs_state || {}
|
|
var history = Object.keys(that.url_history).reverse()
|
|
|
|
// pinned items...
|
|
var list = history
|
|
.filter(function(p){
|
|
// NOTE: yes direct access is faster, but
|
|
// calling the toggler (common API) here
|
|
// will isolate the level knowledge to a
|
|
// single point which will simplify things
|
|
// if anything changes...
|
|
//return that.url_history[p].pinned
|
|
return that.toggleURLPinned(p, '?') == 'on'
|
|
})
|
|
.map(function(p){
|
|
// prevent from drawing again...
|
|
history.splice(history.indexOf(p), 1)
|
|
|
|
// see of we need a full refresh or use the
|
|
// last fs_state...
|
|
if(p in fs_state){
|
|
// XXX need to make this faster...
|
|
var d = fs_state[p]
|
|
|
|
} else {
|
|
var d = !that.checkURLFromHistory(p)
|
|
fs_state[p] = d
|
|
}
|
|
|
|
return [p,
|
|
[p == cur ? 'highlighted selected': '', 'pinned'].join(' '),
|
|
{ disabled: d }
|
|
]
|
|
})
|
|
|
|
// separator...
|
|
list.push([ '---', 'pinned-separator', {}])
|
|
|
|
// history...
|
|
list = list.concat(history
|
|
// NOTE: this might get a little slow for
|
|
// very large sets...
|
|
.map(function(p){
|
|
// see of we need a full refresh or use the
|
|
// last fs_state...
|
|
if(p in fs_state){
|
|
// XXX need to make this faster...
|
|
var d = fs_state[p]
|
|
|
|
} else {
|
|
var d = !that.checkURLFromHistory(p)
|
|
fs_state[p] = d
|
|
}
|
|
|
|
return [p,
|
|
p == cur ? 'highlighted selected': '',
|
|
{disabled: d},
|
|
]
|
|
}))
|
|
|
|
// history is empty...
|
|
// NOTE: the length here is 1 because we need to account
|
|
// for the separator...
|
|
if(list.length == 1){
|
|
list.push([
|
|
'No history...',
|
|
{
|
|
disabled: true,
|
|
buttons: [],
|
|
}
|
|
])
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
|
|
// NOTE: this partially re-implements browse.Items.EditableList(..)
|
|
// but since we have the pinned items we can't use it directly
|
|
// ...and since .EditableList(..) can't be used twice per
|
|
// dialog we can't work around this...
|
|
var o = browse.makeLister(null,
|
|
function(path, make){
|
|
makeHistoryList()
|
|
.forEach(function(elem){
|
|
var e = elem.slice()
|
|
var path = e.shift()
|
|
var cfg = e.pop()
|
|
var cls = e.pop() || ''
|
|
|
|
make(path, cfg)
|
|
.attr('path', path)
|
|
.addClass(cls)
|
|
})
|
|
},
|
|
// add item buttons...
|
|
{ itemButtons: [
|
|
// open...
|
|
['<span class="show-on-hover">↗</span>',
|
|
function(p){
|
|
o.browsePath(p) }],
|
|
// move to top...
|
|
//['<span class="show-on-hover">♦</span>',
|
|
['♦',
|
|
function(p){
|
|
that.setTopURLHistory(p)
|
|
o.redraw(p)
|
|
}],
|
|
// pin to top...
|
|
// XXX should this be standard functionality???
|
|
['<span class="pin-set">●</span>'
|
|
+'<span class="pin-unset">○</span>',
|
|
function(p, cur){
|
|
// change state...
|
|
// pinned...
|
|
if(cur.hasClass('pinned')){
|
|
cur.removeClass('pinned')
|
|
that.toggleURLPinned(p, 'off')
|
|
|
|
// not pinned...
|
|
} else {
|
|
cur.addClass('pinned')
|
|
that.toggleURLPinned(p, 'on')
|
|
}
|
|
|
|
// focus...
|
|
that.config['url-history-focus-on-pin']
|
|
&& o.select(cur)
|
|
|
|
// place...
|
|
o.redraw()
|
|
}],
|
|
// mark for removal...
|
|
browse.buttons.markForRemoval(to_remove)
|
|
] })
|
|
.open(function(evt, path){
|
|
removeStriked('open')
|
|
|
|
o.close()
|
|
|
|
that.openURLFromHistory(path)
|
|
})
|
|
.on('close', function(){
|
|
removeStriked('close')
|
|
})
|
|
|
|
// Monkey-patch: fast redraw...
|
|
//
|
|
// NOTE: this is substantially faster than calling .update()
|
|
// because this will only change positions of a view dom
|
|
// elements while .update(..) will redraw the while thing...
|
|
// NOTE: this also uses fs_state for caching...
|
|
o.redraw = function(path){
|
|
var list = o.dom.find('.list')
|
|
makeHistoryList(fs_state)
|
|
.forEach(function(elem, i){
|
|
if(path && path != elem[0]){
|
|
return
|
|
}
|
|
// move...
|
|
if(list.children().eq(i).attr('path') != elem[0]){
|
|
list.children().eq(i)
|
|
.before(list
|
|
.find('[path="'+elem[0]+'"]'))
|
|
}
|
|
})
|
|
return this
|
|
}
|
|
|
|
// handle 'O' button to browse path...
|
|
o.browsePath = function(p){
|
|
that.browsePath(p || this.selected)
|
|
.close(function(evt, reason){
|
|
reason != 'reject'
|
|
&& o.close(reason)
|
|
}) }
|
|
o.keyboard.handler('General', 'O', 'browsePath')
|
|
|
|
return o
|
|
})],
|
|
|
|
// XXX need to sort pins...
|
|
// XXX need to save pins...
|
|
listURLHistory2: ['History|File/Location history (new)...',
|
|
widgets.makeUIDialog(function(){
|
|
var that = this
|
|
|
|
var data
|
|
var cur = this.location.path
|
|
var to_remove = []
|
|
|
|
// save state:
|
|
// - save pins and pin order... (XXX)
|
|
// - remove stirked out elements...
|
|
var save = function(){
|
|
// XXX save pins/order...
|
|
data.pins.forEach(function(p){
|
|
//that.toggleURLPinned('on')
|
|
})
|
|
|
|
// remove items...
|
|
to_remove.forEach(function(e){
|
|
that.dropURLFromHistory(e)
|
|
})
|
|
to_remove = []
|
|
}
|
|
var makeHistoryList = function(fs_state){
|
|
fs_state = fs_state || {}
|
|
var history = Object.keys(that.url_history).reverse()
|
|
|
|
// XXX need pin order...
|
|
var pinned = []
|
|
var list = history
|
|
// NOTE: this might get a little slow for
|
|
// very large sets...
|
|
.map(function(p){
|
|
// pinned items...
|
|
// NOTE: yes direct access is faster, but
|
|
// calling the toggler (common API) here
|
|
// will isolate the level knowledge to a
|
|
// single point which will simplify things
|
|
// if anything changes...
|
|
//that.url_history[p].pinned ?
|
|
// XXX need pin order...
|
|
that.toggleURLPinned(p, '?') == 'on' ?
|
|
// XXX
|
|
pinned.push(p)
|
|
: null
|
|
|
|
return p
|
|
})
|
|
|
|
return {
|
|
paths: list,
|
|
pins: pinned,
|
|
}
|
|
}
|
|
var makeDisabledChecker = function(fs_state){
|
|
return function(url){
|
|
// see of we need a full refresh or use the
|
|
// last fs_state...
|
|
if(url in fs_state){
|
|
var d = fs_state[url]
|
|
|
|
} else {
|
|
var d = !that.checkURLFromHistory(url)
|
|
fs_state[url] = d
|
|
}
|
|
return d
|
|
}
|
|
}
|
|
|
|
// persistent state...
|
|
var fs_state = {}
|
|
|
|
var dialog = browse.makeLister(null, function(path, make){
|
|
|
|
// live update...
|
|
// XXX add option to force full update...
|
|
data = data == null ? makeHistoryList(fs_state) : data
|
|
|
|
// empty list...
|
|
if(data.paths.length == 0){
|
|
make.Action('No history...', {disabled: true})
|
|
|
|
} else {
|
|
make.EditablePinnedList(data.paths, data.pins, {
|
|
list_id: 'history',
|
|
new_item: false,
|
|
pins_sortable: true,
|
|
|
|
isItemDisabled: makeDisabledChecker(fs_state),
|
|
|
|
to_remove: to_remove,
|
|
|
|
/*/ XXX not sure if this is the right way...
|
|
pins_buttons: [
|
|
// open...
|
|
['<span class="show-on-hover">↗</span>',
|
|
function(p){ dialog.browsePath(p) }],
|
|
'PIN',
|
|
'REMOVE',
|
|
],
|
|
//*/
|
|
buttons: [
|
|
// open...
|
|
['<span class="show-on-hover">↗</span>',
|
|
function(p){ dialog.browsePath(p) }],
|
|
['♦', 'TO_TOP'],
|
|
'PIN',
|
|
'REMOVE',
|
|
],
|
|
})
|
|
}
|
|
|
|
make
|
|
.done()
|
|
// highlight the current item...
|
|
.then(function(){
|
|
dialog
|
|
.filter(`"${cur}"`)
|
|
.addClass('highlighted') })
|
|
},
|
|
{
|
|
selected: cur,
|
|
})
|
|
.open(function(evt, path){
|
|
save()
|
|
|
|
dialog.close()
|
|
|
|
that.openURLFromHistory(path)
|
|
})
|
|
.on('close', function(){
|
|
save()
|
|
})
|
|
|
|
// handle 'O' button to browse path...
|
|
dialog.browsePath = function(p){
|
|
this.selected
|
|
&& that.browsePath(p || this.selected)
|
|
.close(function(evt, reason){
|
|
reason != 'reject'
|
|
&& dialog.close(reason)
|
|
}) }
|
|
dialog.keyboard.handler('General', 'O', 'browsePath')
|
|
|
|
return dialog
|
|
})],
|
|
})
|
|
|
|
var URLHistoryUI =
|
|
module.URLHistoryUI = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'ui-url-history',
|
|
depends: [
|
|
'ui',
|
|
'url-history',
|
|
],
|
|
actions: URLHistoryUIActions,
|
|
})
|
|
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */ return module })
|