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:
Alex A. Naanou 2013-09-25 02:08:25 +04:00
parent cf9d6b301f
commit d2a065b09a
6 changed files with 461 additions and 412 deletions

193
ui/crop.js Executable file
View 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 : */

View File

@ -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 : */

View File

@ -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
View 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
View 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 : */