mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-30 10:50:08 +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 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 CROP_STACK = []
|
||||
|
||||
var CROP_MODES = []
|
||||
|
||||
// NOTE: these are named: <mode>-<feature>
|
||||
var SETTINGS = {
|
||||
'global-theme': null,
|
||||
@ -146,14 +146,6 @@ var UPDATE_SORT_ENABLED = false
|
||||
// XXX for some reason the sync version appears to work faster...
|
||||
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...
|
||||
*/
|
||||
|
||||
/******************************************************* 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 ***/
|
||||
|
||||
// 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 : */
|
||||
|
||||
@ -21,8 +21,11 @@
|
||||
|
||||
<script src="compatibility.js"></script>
|
||||
|
||||
<script src="base.js"></script>
|
||||
<script src="ribbons.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="marks.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