mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-30 19:00:09 +00:00
refactoring: splitup data.js into several modules, still not clean-enough...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
cf9d6b301f
commit
d2a065b09a
193
ui/crop.js
Executable file
193
ui/crop.js
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
*
|
||||||
|
* Ribbon Crop API
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
var CROP_STACK = []
|
||||||
|
|
||||||
|
var CROP_MODES = []
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************* Crop Data ***/
|
||||||
|
|
||||||
|
function isViewCropped(){
|
||||||
|
return CROP_STACK.length != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getAllData(){
|
||||||
|
if(!isViewCropped()){
|
||||||
|
return DATA
|
||||||
|
} else {
|
||||||
|
return CROP_STACK[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: this will not update .current state...
|
||||||
|
// NOTE: when keep_ribbons is set, this may generate empty ribbons...
|
||||||
|
//
|
||||||
|
// XXX should this set the .current to anything but null or the first elem???
|
||||||
|
function makeCroppedData(gids, keep_ribbons){
|
||||||
|
var res = {
|
||||||
|
varsion: '2.0',
|
||||||
|
current: null,
|
||||||
|
ribbons: [],
|
||||||
|
order: DATA.order.slice(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// flat single ribbon crop...
|
||||||
|
if(!keep_ribbons){
|
||||||
|
res.ribbons[0] = gids
|
||||||
|
|
||||||
|
// keep the ribbon structure...
|
||||||
|
} else {
|
||||||
|
$.each(DATA.ribbons, function(_, e){
|
||||||
|
e = e.filter(function(ee){ return gids.indexOf(ee) >= 0 })
|
||||||
|
// skip empty ribbons...
|
||||||
|
if(e.length != 0){
|
||||||
|
res.ribbons.push(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: if keep_ribbons is not set this will ALWAYS build a single ribbon
|
||||||
|
// data-set...
|
||||||
|
function cropDataTo(gids, keep_ribbons){
|
||||||
|
var prev_state = DATA
|
||||||
|
var cur = DATA.current
|
||||||
|
var r = getRibbonIndex()
|
||||||
|
|
||||||
|
var new_data = makeCroppedData(gids, keep_ribbons)
|
||||||
|
|
||||||
|
// do nothing if there is no change...
|
||||||
|
// XXX is there a better way to compare states???
|
||||||
|
if(JSON.stringify(DATA.ribbons) == JSON.stringify(new_data.ribbons)){
|
||||||
|
return DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
CROP_STACK.push(prev_state)
|
||||||
|
DATA = new_data
|
||||||
|
|
||||||
|
cur = getGIDBefore(cur, keep_ribbons ? r : 0)
|
||||||
|
cur = cur == null ? gids[0] : cur
|
||||||
|
DATA.current = cur
|
||||||
|
|
||||||
|
reloadViewer()
|
||||||
|
updateImages()
|
||||||
|
|
||||||
|
return prev_state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function uncropData(){
|
||||||
|
if(!isViewCropped()){
|
||||||
|
return DATA
|
||||||
|
}
|
||||||
|
var prev_state = DATA
|
||||||
|
var cur = DATA.current
|
||||||
|
|
||||||
|
DATA = CROP_STACK.pop()
|
||||||
|
|
||||||
|
// check if cur exists in data being loaded...
|
||||||
|
if($.map(DATA.ribbons,
|
||||||
|
function(e, i){ return e.indexOf(cur) >= 0 }).indexOf(true) >= 0){
|
||||||
|
// keep the current position...
|
||||||
|
DATA.current = cur
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadViewer()
|
||||||
|
updateImages()
|
||||||
|
|
||||||
|
return prev_state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showAllData(){
|
||||||
|
var prev_state = DATA
|
||||||
|
var cur = DATA.current
|
||||||
|
|
||||||
|
if(CROP_STACK.length != 0){
|
||||||
|
DATA = getAllData()
|
||||||
|
CROP_STACK = []
|
||||||
|
|
||||||
|
// XXX do we need to check if this exists???
|
||||||
|
// ...in theory, as long as there are no global destructive
|
||||||
|
// operations, no.
|
||||||
|
// keep the current position...
|
||||||
|
DATA.current = cur
|
||||||
|
|
||||||
|
reloadViewer()
|
||||||
|
updateImages()
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev_state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers for making crop modes and using crop...
|
||||||
|
|
||||||
|
// Make a generic crop mode toggler
|
||||||
|
//
|
||||||
|
// NOTE: This will add the toggler to CROP_MODES, for use by
|
||||||
|
// uncropLastState(...)
|
||||||
|
// NOTE: crop modes are exclusive -- it is not possible to enter one crop
|
||||||
|
// mode from a different crop mode
|
||||||
|
function makeCropModeToggler(cls, crop){
|
||||||
|
var res = createCSSClassToggler(
|
||||||
|
'.viewer',
|
||||||
|
//cls + ' cropped-mode',
|
||||||
|
cls,
|
||||||
|
function(action){
|
||||||
|
// prevent mixing marked-only and single-ribbon modes...
|
||||||
|
if(action == 'on'
|
||||||
|
&& isViewCropped()
|
||||||
|
&& res('?') != 'on'){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(action){
|
||||||
|
if(action == 'on'){
|
||||||
|
showStatusQ('Cropping current ribbon...')
|
||||||
|
crop()
|
||||||
|
} else {
|
||||||
|
showStatusQ('Uncropping to all data...')
|
||||||
|
showAllData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
CROP_MODES.push(res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Uncrop to last state and there is no states to uncrop then exit
|
||||||
|
// cropped mode.
|
||||||
|
//
|
||||||
|
// NOTE: this will exit all crop modes when uncropping the last step.
|
||||||
|
function uncropLastState(){
|
||||||
|
// do nothing if we aren't in a crop mode...
|
||||||
|
if(!isViewCropped()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit cropped all modes...
|
||||||
|
if(CROP_STACK.length == 1){
|
||||||
|
$.each(CROP_MODES, function(_, e){ e('off') })
|
||||||
|
|
||||||
|
// ucrop one state...
|
||||||
|
} else {
|
||||||
|
showStatusQ('Uncropping...')
|
||||||
|
uncropData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* vim:set ts=4 sw=4 : */
|
||||||
415
ui/data.js
415
ui/data.js
@ -1,7 +1,11 @@
|
|||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
*
|
*
|
||||||
|
* Data API and Data DOM connections...
|
||||||
*
|
*
|
||||||
* TODO move DATA to a more logical context avoiding the global vars...
|
* TODO move DATA to a more logical context avoiding the global vars...
|
||||||
|
* TODO try and split this into:
|
||||||
|
* - data.js -- pure DATA API
|
||||||
|
* - data-ribbons.js -- DATA and Ribbon API mashup...
|
||||||
*
|
*
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
@ -109,10 +113,6 @@ var IMAGES_CREATED = false
|
|||||||
|
|
||||||
var MARKED = []
|
var MARKED = []
|
||||||
|
|
||||||
var CROP_STACK = []
|
|
||||||
|
|
||||||
var CROP_MODES = []
|
|
||||||
|
|
||||||
// NOTE: these are named: <mode>-<feature>
|
// NOTE: these are named: <mode>-<feature>
|
||||||
var SETTINGS = {
|
var SETTINGS = {
|
||||||
'global-theme': null,
|
'global-theme': null,
|
||||||
@ -146,14 +146,6 @@ var UPDATE_SORT_ENABLED = false
|
|||||||
// XXX for some reason the sync version appears to work faster...
|
// XXX for some reason the sync version appears to work faster...
|
||||||
var UPDATE_SYNC = false
|
var UPDATE_SYNC = false
|
||||||
|
|
||||||
// object to register all the worker queues...
|
|
||||||
var WORKERS = {}
|
|
||||||
|
|
||||||
// Used in sortImagesByFileNameSeqWithOverflow
|
|
||||||
var PROXIMITY = 30
|
|
||||||
var CHECK_1ST_PROXIMITY = false
|
|
||||||
var OVERFLOW_GAP = PROXIMITY * 5
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
@ -1596,221 +1588,6 @@ function getPrevLocation(){
|
|||||||
* Actions...
|
* Actions...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/******************************************************* Crop Data ***/
|
|
||||||
|
|
||||||
function isViewCropped(){
|
|
||||||
return CROP_STACK.length != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getAllData(){
|
|
||||||
if(!isViewCropped()){
|
|
||||||
return DATA
|
|
||||||
} else {
|
|
||||||
return CROP_STACK[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: this will not update .current state...
|
|
||||||
// NOTE: when keep_ribbons is set, this may generate empty ribbons...
|
|
||||||
//
|
|
||||||
// XXX should this set the .current to anything but null or the first elem???
|
|
||||||
function makeCroppedData(gids, keep_ribbons){
|
|
||||||
var res = {
|
|
||||||
varsion: '2.0',
|
|
||||||
current: null,
|
|
||||||
ribbons: [],
|
|
||||||
order: DATA.order.slice(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// flat single ribbon crop...
|
|
||||||
if(!keep_ribbons){
|
|
||||||
res.ribbons[0] = gids
|
|
||||||
|
|
||||||
// keep the ribbon structure...
|
|
||||||
} else {
|
|
||||||
$.each(DATA.ribbons, function(_, e){
|
|
||||||
e = e.filter(function(ee){ return gids.indexOf(ee) >= 0 })
|
|
||||||
// skip empty ribbons...
|
|
||||||
if(e.length != 0){
|
|
||||||
res.ribbons.push(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: if keep_ribbons is not set this will ALWAYS build a single ribbon
|
|
||||||
// data-set...
|
|
||||||
function cropDataTo(gids, keep_ribbons){
|
|
||||||
var prev_state = DATA
|
|
||||||
var cur = DATA.current
|
|
||||||
var r = getRibbonIndex()
|
|
||||||
|
|
||||||
var new_data = makeCroppedData(gids, keep_ribbons)
|
|
||||||
|
|
||||||
// do nothing if there is no change...
|
|
||||||
// XXX is there a better way to compare states???
|
|
||||||
if(JSON.stringify(DATA.ribbons) == JSON.stringify(new_data.ribbons)){
|
|
||||||
return DATA
|
|
||||||
}
|
|
||||||
|
|
||||||
CROP_STACK.push(prev_state)
|
|
||||||
DATA = new_data
|
|
||||||
|
|
||||||
cur = getGIDBefore(cur, keep_ribbons ? r : 0)
|
|
||||||
cur = cur == null ? gids[0] : cur
|
|
||||||
DATA.current = cur
|
|
||||||
|
|
||||||
reloadViewer()
|
|
||||||
updateImages()
|
|
||||||
|
|
||||||
return prev_state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function uncropData(){
|
|
||||||
if(!isViewCropped()){
|
|
||||||
return DATA
|
|
||||||
}
|
|
||||||
var prev_state = DATA
|
|
||||||
var cur = DATA.current
|
|
||||||
|
|
||||||
DATA = CROP_STACK.pop()
|
|
||||||
|
|
||||||
// check if cur exists in data being loaded...
|
|
||||||
if($.map(DATA.ribbons,
|
|
||||||
function(e, i){ return e.indexOf(cur) >= 0 }).indexOf(true) >= 0){
|
|
||||||
// keep the current position...
|
|
||||||
DATA.current = cur
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadViewer()
|
|
||||||
updateImages()
|
|
||||||
|
|
||||||
return prev_state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function showAllData(){
|
|
||||||
var prev_state = DATA
|
|
||||||
var cur = DATA.current
|
|
||||||
|
|
||||||
if(CROP_STACK.length != 0){
|
|
||||||
DATA = getAllData()
|
|
||||||
CROP_STACK = []
|
|
||||||
|
|
||||||
// XXX do we need to check if this exists???
|
|
||||||
// ...in theory, as long as there are no global destructive
|
|
||||||
// operations, no.
|
|
||||||
// keep the current position...
|
|
||||||
DATA.current = cur
|
|
||||||
|
|
||||||
reloadViewer()
|
|
||||||
updateImages()
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev_state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Helpers for making crop modes and using crop...
|
|
||||||
|
|
||||||
// Make a generic crop mode toggler
|
|
||||||
//
|
|
||||||
// NOTE: This will add the toggler to CROP_MODES, for use by
|
|
||||||
// uncropLastState(...)
|
|
||||||
// NOTE: crop modes are exclusive -- it is not possible to enter one crop
|
|
||||||
// mode from a different crop mode
|
|
||||||
function makeCropModeToggler(cls, crop){
|
|
||||||
var res = createCSSClassToggler(
|
|
||||||
'.viewer',
|
|
||||||
//cls + ' cropped-mode',
|
|
||||||
cls,
|
|
||||||
function(action){
|
|
||||||
// prevent mixing marked-only and single-ribbon modes...
|
|
||||||
if(action == 'on'
|
|
||||||
&& isViewCropped()
|
|
||||||
&& res('?') != 'on'){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(action){
|
|
||||||
if(action == 'on'){
|
|
||||||
showStatusQ('Cropping current ribbon...')
|
|
||||||
crop()
|
|
||||||
} else {
|
|
||||||
showStatusQ('Uncropping to all data...')
|
|
||||||
showAllData()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
CROP_MODES.push(res)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Uncrop to last state and there is no states to uncrop then exit
|
|
||||||
// cropped mode.
|
|
||||||
//
|
|
||||||
// NOTE: this will exit all crop modes when uncropping the last step.
|
|
||||||
function uncropLastState(){
|
|
||||||
// do nothing if we aren't in a crop mode...
|
|
||||||
if(!isViewCropped()){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit cropped all modes...
|
|
||||||
if(CROP_STACK.length == 1){
|
|
||||||
$.each(CROP_MODES, function(_, e){ e('off') })
|
|
||||||
|
|
||||||
// ucrop one state...
|
|
||||||
} else {
|
|
||||||
showStatusQ('Uncropping...')
|
|
||||||
uncropData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************************************* Workers ***/
|
|
||||||
|
|
||||||
// get/create a named worker queue...
|
|
||||||
function getWorkerQueue(name, no_auto_start){
|
|
||||||
|
|
||||||
// create a new worker queue...
|
|
||||||
if(WORKERS[name] == null){
|
|
||||||
var queue = makeDeferredsQ()
|
|
||||||
WORKERS[name] = queue
|
|
||||||
// start if needed...
|
|
||||||
if(!no_auto_start){
|
|
||||||
queue.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// return existing worker queue...
|
|
||||||
} else {
|
|
||||||
var queue = WORKERS[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
return queue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// kill all worker queues...
|
|
||||||
function killAllWorkers(){
|
|
||||||
for(var k in WORKERS){
|
|
||||||
if(WORKERS[k].isWorking()){
|
|
||||||
console.log('Worker: Stopped:', k)
|
|
||||||
}
|
|
||||||
WORKERS[k].kill()
|
|
||||||
}
|
|
||||||
WORKERS = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/******************************************************* Extension ***/
|
/******************************************************* Extension ***/
|
||||||
|
|
||||||
// Open image in an external editor/viewer
|
// Open image in an external editor/viewer
|
||||||
@ -1833,189 +1610,5 @@ function openImageWith(prog){
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************************************* Sorting ***/
|
|
||||||
|
|
||||||
function reverseImageOrder(){
|
|
||||||
DATA.order.reverse()
|
|
||||||
updateRibbonOrder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: using imageOrderCmp as a cmp function here will yield odd
|
|
||||||
// results -- in-place sorting a list based on relative element
|
|
||||||
// positions within itself is fun ;)
|
|
||||||
function sortImages(cmp, reverse){
|
|
||||||
cmp = cmp == null ? imageDateCmp : cmp
|
|
||||||
DATA.order.sort(cmp)
|
|
||||||
if(reverse){
|
|
||||||
DATA.order.reverse()
|
|
||||||
}
|
|
||||||
updateRibbonOrder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// shorthands...
|
|
||||||
function sortImagesByDate(reverse){
|
|
||||||
return sortImages(reverse)
|
|
||||||
}
|
|
||||||
function sortImagesByFileName(reverse){
|
|
||||||
return sortImages(imageNameCmp, reverse)
|
|
||||||
}
|
|
||||||
function sortImagesByFileSeqOrName(reverse){
|
|
||||||
return sortImages(imageSeqOrNameCmp, reverse)
|
|
||||||
}
|
|
||||||
function sortImagesByFileNameXPStyle(reverse){
|
|
||||||
return sortImages(imageXPStyleFileNameCmp, reverse)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Sort images by name while taking into account sequence overflows
|
|
||||||
//
|
|
||||||
// A name sequence overflow is when file name sequence overflows over
|
|
||||||
// *9999 and then resets to *0001...
|
|
||||||
//
|
|
||||||
// For this to be applicable:
|
|
||||||
// - ALL filenames must contain a sequence number
|
|
||||||
// XXX do we need to make this more strict?
|
|
||||||
// ...for example make name.split(<seq>) equal for all files
|
|
||||||
// - the total number of files in sequence is < 10K
|
|
||||||
// XXX a simplification...
|
|
||||||
// there could be more than 10K images but then we will need to
|
|
||||||
// either take dates or folder names into account...
|
|
||||||
// - the lowest filename in set must be near seq 0001
|
|
||||||
// - the highest filename in set must be near seq 9999
|
|
||||||
// - there must be a gap somewhere in the set
|
|
||||||
// this gap size is roughly close to 10K - N where N is the total
|
|
||||||
// number of files in set
|
|
||||||
// XXX a simplification...
|
|
||||||
//
|
|
||||||
// The gap size must be above overflow_gap, and if it is set to false
|
|
||||||
// then no limit is used (default: OVERFLOW_GAP).
|
|
||||||
// If check_1st is false then also check the lowest sequence number
|
|
||||||
// for proximity to 0001 (default: CHECK_1ST_PROXIMITY).
|
|
||||||
//
|
|
||||||
// NOTE: if any of the above conditions is not applicable this will
|
|
||||||
// essentially revert to sortImagesByFileSeqOrName(...)
|
|
||||||
// NOTE: this will cut at the largest gap between sequence numbers.
|
|
||||||
//
|
|
||||||
// XXX it would be a good idea to account for folder name sequencing...
|
|
||||||
// XXX it's also a good idea to write an image serial number sort...
|
|
||||||
// XXX is this overcomplicated???
|
|
||||||
//
|
|
||||||
// NOTE: I like this piece if code, if it works correctly no one will
|
|
||||||
// ever know it's here, if we replace it with the thee line dumb
|
|
||||||
// sortImagesByFileName(...) then things get "annoying" every 10K
|
|
||||||
// images :)
|
|
||||||
function sortImagesByFileNameSeqWithOverflow(reverse, proximity, overflow_gap, check_1st){
|
|
||||||
proximity = proximity == null ? PROXIMITY : proximity
|
|
||||||
overflow_gap = overflow_gap == null ? OVERFLOW_GAP : overflow_gap
|
|
||||||
check_1st = check_1st == null ? CHECK_1ST_PROXIMITY : check_1st
|
|
||||||
|
|
||||||
// prepare to sort and check names...
|
|
||||||
// NOTE: we do not usually have a filename seq 0000...
|
|
||||||
if(DATA.order.length < 9999){
|
|
||||||
var need_to_fix = true
|
|
||||||
|
|
||||||
function cmp(a, b){
|
|
||||||
if(need_to_fix){
|
|
||||||
if(typeof(getImageNameSeq(a)) == typeof('str')
|
|
||||||
|| typeof(getImageNameSeq(b)) == typeof('str')){
|
|
||||||
need_to_fix = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imageSeqOrNameCmp(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// revert to normal sort my name...
|
|
||||||
} else {
|
|
||||||
// XXX make this more cleaver -- split the set into 10K chunks and
|
|
||||||
// sort the chunks too...
|
|
||||||
return sortImagesByFileName(reverse)
|
|
||||||
}
|
|
||||||
|
|
||||||
DATA.order.sort(cmp)
|
|
||||||
|
|
||||||
// find and fix the gap...
|
|
||||||
if(need_to_fix
|
|
||||||
// check if first and last are close to 0001 and 9999 resp.
|
|
||||||
&& (!check_1st || getImageNameSeq(DATA.order[0]) <= proximity)
|
|
||||||
&& getImageNameSeq(DATA.order[DATA.order.length-1]) >= 9999-proximity){
|
|
||||||
// find the largest gap position...
|
|
||||||
var pos = null
|
|
||||||
var gap = 0
|
|
||||||
for(var i=1; i<DATA.order.length; i++){
|
|
||||||
var n_gap = Math.max(getImageNameSeq(DATA.order[i])-getImageNameSeq(DATA.order[i-1]), gap)
|
|
||||||
if(n_gap != gap){
|
|
||||||
pos = i
|
|
||||||
gap = n_gap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// split and rearrange the order chunks...
|
|
||||||
if(overflow_gap === false || gap > overflow_gap){
|
|
||||||
DATA.order = DATA.order.splice(pos).concat(DATA.order)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(reverse){
|
|
||||||
DATA.order.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRibbonOrder()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/************************************************** Manual sorting ***/
|
|
||||||
|
|
||||||
// Ordering images...
|
|
||||||
// NOTE: this a bit more complicated than simply shifting an image
|
|
||||||
// left/right the DATA.order, we have to put it before or after
|
|
||||||
// the prev/next image...
|
|
||||||
function horizontalShiftImage(image, direction){
|
|
||||||
image = image == null ? getImage() : $(image)
|
|
||||||
var gid = getImageGID(image)
|
|
||||||
var r = getRibbonIndex(image)
|
|
||||||
var ri = DATA.ribbons[r].indexOf(gid)
|
|
||||||
|
|
||||||
// the image we are going to move relative to...
|
|
||||||
var target = DATA.ribbons[r][ri + (direction == 'next' ? 1 : -1)]
|
|
||||||
|
|
||||||
// we can hit the end or start of the ribbon...
|
|
||||||
if(target == null){
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the order...
|
|
||||||
// NOTE: this is a critical section and must be done as fast as possible,
|
|
||||||
// this is why we are using the memory to first do the work and
|
|
||||||
// then push it in...
|
|
||||||
// NOTE: in a race condition this may still overwrite the order someone
|
|
||||||
// else is working on, the data will be consistent...
|
|
||||||
var order = DATA.order.slice()
|
|
||||||
order.splice(order.indexOf(gid), 1)
|
|
||||||
order.splice(order.indexOf(target) + (direction == 'next'? 1 : 0), 0, gid)
|
|
||||||
// do the dirty work...
|
|
||||||
DATA.order.splice.apply(DATA.order, [0, DATA.order.length].concat(order))
|
|
||||||
|
|
||||||
// just update the ribbons, no reloading needed...
|
|
||||||
updateRibbonOrder(true)
|
|
||||||
|
|
||||||
// shift the images...
|
|
||||||
getImage(target)[direction == 'prev' ? 'before' : 'after'](image)
|
|
||||||
|
|
||||||
// update stuff that changed, mainly order...
|
|
||||||
updateImages()
|
|
||||||
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
function shiftImageLeft(image){
|
|
||||||
return horizontalShiftImage(image, 'prev')
|
|
||||||
}
|
|
||||||
function shiftImageRight(image){
|
|
||||||
return horizontalShiftImage(image, 'next')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* vim:set ts=4 sw=4 spell : */
|
* vim:set ts=4 sw=4 spell : */
|
||||||
|
|||||||
@ -21,8 +21,11 @@
|
|||||||
|
|
||||||
<script src="compatibility.js"></script>
|
<script src="compatibility.js"></script>
|
||||||
|
|
||||||
<script src="base.js"></script>
|
<script src="ribbons.js"></script>
|
||||||
<script src="data.js"></script>
|
<script src="data.js"></script>
|
||||||
|
<script src="crop.js"></script>
|
||||||
|
<script src="sort.js"></script>
|
||||||
|
<script src="workers.js"></script>
|
||||||
<script src="modes.js"></script>
|
<script src="modes.js"></script>
|
||||||
<script src="marks.js"></script>
|
<script src="marks.js"></script>
|
||||||
<script src="files.js"></script>
|
<script src="files.js"></script>
|
||||||
|
|||||||
208
ui/sort.js
Executable file
208
ui/sort.js
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
*
|
||||||
|
* Image Sorting API
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
// Used in sortImagesByFileNameSeqWithOverflow
|
||||||
|
var PROXIMITY = 30
|
||||||
|
var CHECK_1ST_PROXIMITY = false
|
||||||
|
var OVERFLOW_GAP = PROXIMITY * 5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************* Sorting ***/
|
||||||
|
|
||||||
|
function reverseImageOrder(){
|
||||||
|
DATA.order.reverse()
|
||||||
|
updateRibbonOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: using imageOrderCmp as a cmp function here will yield odd
|
||||||
|
// results -- in-place sorting a list based on relative element
|
||||||
|
// positions within itself is fun ;)
|
||||||
|
function sortImages(cmp, reverse){
|
||||||
|
cmp = cmp == null ? imageDateCmp : cmp
|
||||||
|
DATA.order.sort(cmp)
|
||||||
|
if(reverse){
|
||||||
|
DATA.order.reverse()
|
||||||
|
}
|
||||||
|
updateRibbonOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// shorthands...
|
||||||
|
function sortImagesByDate(reverse){
|
||||||
|
return sortImages(reverse)
|
||||||
|
}
|
||||||
|
function sortImagesByFileName(reverse){
|
||||||
|
return sortImages(imageNameCmp, reverse)
|
||||||
|
}
|
||||||
|
function sortImagesByFileSeqOrName(reverse){
|
||||||
|
return sortImages(imageSeqOrNameCmp, reverse)
|
||||||
|
}
|
||||||
|
function sortImagesByFileNameXPStyle(reverse){
|
||||||
|
return sortImages(imageXPStyleFileNameCmp, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sort images by name while taking into account sequence overflows
|
||||||
|
//
|
||||||
|
// A name sequence overflow is when file name sequence overflows over
|
||||||
|
// *9999 and then resets to *0001...
|
||||||
|
//
|
||||||
|
// For this to be applicable:
|
||||||
|
// - ALL filenames must contain a sequence number
|
||||||
|
// XXX do we need to make this more strict?
|
||||||
|
// ...for example make name.split(<seq>) equal for all files
|
||||||
|
// - the total number of files in sequence is < 10K
|
||||||
|
// XXX a simplification...
|
||||||
|
// there could be more than 10K images but then we will need to
|
||||||
|
// either take dates or folder names into account...
|
||||||
|
// - the lowest filename in set must be near seq 0001
|
||||||
|
// - the highest filename in set must be near seq 9999
|
||||||
|
// XXX alternatively check the difference between first and
|
||||||
|
// last elements, if it is far greater than the number
|
||||||
|
// of elements then it's likely that we need to split...
|
||||||
|
// - there must be a gap somewhere in the set
|
||||||
|
// this gap size is roughly close to 10K - N where N is the total
|
||||||
|
// number of files in set
|
||||||
|
// XXX a simplification...
|
||||||
|
//
|
||||||
|
// The gap size must be above overflow_gap, and if it is set to false
|
||||||
|
// then no limit is used (default: OVERFLOW_GAP).
|
||||||
|
// If check_1st is false then also check the lowest sequence number
|
||||||
|
// for proximity to 0001 (default: CHECK_1ST_PROXIMITY).
|
||||||
|
//
|
||||||
|
// NOTE: if any of the above conditions is not applicable this will
|
||||||
|
// essentially revert to sortImagesByFileSeqOrName(...)
|
||||||
|
// NOTE: this will cut at the largest gap between sequence numbers.
|
||||||
|
//
|
||||||
|
// XXX it would be a good idea to account for folder name sequencing...
|
||||||
|
// XXX it's also a good idea to write an image serial number sort...
|
||||||
|
// XXX is this overcomplicated???
|
||||||
|
//
|
||||||
|
// NOTE: I like this piece if code, if it works correctly no one will
|
||||||
|
// ever know it's here, if we replace it with the thee line dumb
|
||||||
|
// sortImagesByFileName(...) then things get "annoying" every 10K
|
||||||
|
// images :)
|
||||||
|
function sortImagesByFileNameSeqWithOverflow(reverse, proximity, overflow_gap, check_1st){
|
||||||
|
proximity = proximity == null ? PROXIMITY : proximity
|
||||||
|
overflow_gap = overflow_gap == null ? OVERFLOW_GAP : overflow_gap
|
||||||
|
check_1st = check_1st == null ? CHECK_1ST_PROXIMITY : check_1st
|
||||||
|
|
||||||
|
// prepare to sort and check names...
|
||||||
|
// NOTE: we do not usually have a filename seq 0000...
|
||||||
|
if(DATA.order.length < 9999){
|
||||||
|
var need_to_fix = true
|
||||||
|
|
||||||
|
function cmp(a, b){
|
||||||
|
if(need_to_fix){
|
||||||
|
if(typeof(getImageNameSeq(a)) == typeof('str')
|
||||||
|
|| typeof(getImageNameSeq(b)) == typeof('str')){
|
||||||
|
need_to_fix = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageSeqOrNameCmp(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// revert to normal sort my name...
|
||||||
|
} else {
|
||||||
|
// XXX make this more cleaver -- split the set into 10K chunks and
|
||||||
|
// sort the chunks too...
|
||||||
|
return sortImagesByFileName(reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
DATA.order.sort(cmp)
|
||||||
|
|
||||||
|
// find and fix the gap...
|
||||||
|
if(need_to_fix
|
||||||
|
// check if first and last are close to 0001 and 9999 resp.
|
||||||
|
// XXX alternatively check the difference between first and
|
||||||
|
// last elements, if it is far greater than the number
|
||||||
|
// of elements then it's likely that we need to split...
|
||||||
|
&& (!check_1st || getImageNameSeq(DATA.order[0]) <= proximity)
|
||||||
|
&& getImageNameSeq(DATA.order[DATA.order.length-1]) >= 9999-proximity){
|
||||||
|
// find the largest gap position...
|
||||||
|
var pos = null
|
||||||
|
var gap = 0
|
||||||
|
for(var i=1; i<DATA.order.length; i++){
|
||||||
|
var n_gap = Math.max(getImageNameSeq(DATA.order[i])-getImageNameSeq(DATA.order[i-1]), gap)
|
||||||
|
if(n_gap != gap){
|
||||||
|
pos = i
|
||||||
|
gap = n_gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// split and rearrange the order chunks...
|
||||||
|
if(overflow_gap === false || gap > overflow_gap){
|
||||||
|
DATA.order = DATA.order.splice(pos).concat(DATA.order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(reverse){
|
||||||
|
DATA.order.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRibbonOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************** Manual sorting ***/
|
||||||
|
|
||||||
|
// Ordering images...
|
||||||
|
// NOTE: this a bit more complicated than simply shifting an image
|
||||||
|
// left/right the DATA.order, we have to put it before or after
|
||||||
|
// the prev/next image...
|
||||||
|
function horizontalShiftImage(image, direction){
|
||||||
|
image = image == null ? getImage() : $(image)
|
||||||
|
var gid = getImageGID(image)
|
||||||
|
var r = getRibbonIndex(image)
|
||||||
|
var ri = DATA.ribbons[r].indexOf(gid)
|
||||||
|
|
||||||
|
// the image we are going to move relative to...
|
||||||
|
var target = DATA.ribbons[r][ri + (direction == 'next' ? 1 : -1)]
|
||||||
|
|
||||||
|
// we can hit the end or start of the ribbon...
|
||||||
|
if(target == null){
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the order...
|
||||||
|
// NOTE: this is a critical section and must be done as fast as possible,
|
||||||
|
// this is why we are using the memory to first do the work and
|
||||||
|
// then push it in...
|
||||||
|
// NOTE: in a race condition this may still overwrite the order someone
|
||||||
|
// else is working on, the data will be consistent...
|
||||||
|
var order = DATA.order.slice()
|
||||||
|
order.splice(order.indexOf(gid), 1)
|
||||||
|
order.splice(order.indexOf(target) + (direction == 'next'? 1 : 0), 0, gid)
|
||||||
|
// do the dirty work...
|
||||||
|
DATA.order.splice.apply(DATA.order, [0, DATA.order.length].concat(order))
|
||||||
|
|
||||||
|
// just update the ribbons, no reloading needed...
|
||||||
|
updateRibbonOrder(true)
|
||||||
|
|
||||||
|
// shift the images...
|
||||||
|
getImage(target)[direction == 'prev' ? 'before' : 'after'](image)
|
||||||
|
|
||||||
|
// update stuff that changed, mainly order...
|
||||||
|
updateImages()
|
||||||
|
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
function shiftImageLeft(image){
|
||||||
|
return horizontalShiftImage(image, 'prev')
|
||||||
|
}
|
||||||
|
function shiftImageRight(image){
|
||||||
|
return horizontalShiftImage(image, 'next')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* vim:set ts=4 sw=4 : */
|
||||||
52
ui/workers.js
Executable file
52
ui/workers.js
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
*
|
||||||
|
* Deferred worker API
|
||||||
|
*
|
||||||
|
* NOTE: at this point this only contains a worker queue...
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
// object to register all the worker queues...
|
||||||
|
var WORKERS = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************* Workers ***/
|
||||||
|
|
||||||
|
// get/create a named worker queue...
|
||||||
|
function getWorkerQueue(name, no_auto_start){
|
||||||
|
|
||||||
|
// create a new worker queue...
|
||||||
|
if(WORKERS[name] == null){
|
||||||
|
var queue = makeDeferredsQ()
|
||||||
|
WORKERS[name] = queue
|
||||||
|
// start if needed...
|
||||||
|
if(!no_auto_start){
|
||||||
|
queue.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return existing worker queue...
|
||||||
|
} else {
|
||||||
|
var queue = WORKERS[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
return queue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// kill all worker queues...
|
||||||
|
function killAllWorkers(){
|
||||||
|
for(var k in WORKERS){
|
||||||
|
if(WORKERS[k].isWorking()){
|
||||||
|
console.log('Worker: Stopped:', k)
|
||||||
|
}
|
||||||
|
WORKERS[k].kill()
|
||||||
|
}
|
||||||
|
WORKERS = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* vim:set ts=4 sw=4 : */
|
||||||
Loading…
x
Reference in New Issue
Block a user