diff --git a/ui/TODO.otl b/ui/TODO.otl index 3095fa0e..2d8ba2f7 100755 --- a/ui/TODO.otl +++ b/ui/TODO.otl @@ -109,23 +109,7 @@ Roadmap [_] 31% Gen 3 current todo - [_] 62% High priority - [_] Might be a good idea to use sparse arrays for things like marks... - | eliminate: - | - need for keeping things sorted all the time - | - speed-up access -- everything has the same index - | - speed-up modification -- just mirror all the operations - | - searching in more than one place - | - | introduce: - | - memory overhead - each array is always N elements - | - conversion on import, export and crop - | need to clear / insert all the nulls - | - | candidates: - | - marks - | - bookmarks - | - tags + [_] 63% High priority [_] BUG: sorting breaks when at or near the end of a ribbon... | | Race condition... @@ -148,6 +132,8 @@ Roadmap | because there is nothing wrong with sorting itself, just the | UI, the resulting state can be fixed by simply reloading the | viewer (reloadViewer(true) or ctrl-alt-r) + | + | NOTE: appears to affect beginning of the ribbon too... [_] BUG: sorting mis-aligns ribbons in some cases... | Example: | oooo... --[reverse]-> ...oooo @@ -560,6 +546,21 @@ Roadmap | drops to last placeholder | [_] single image mode transition (alpha-blend/fade/none) + [X] Might be a good idea to use sparse arrays for things like marks... + | eliminate: + | - need for keeping things sorted all the time + | - speed-up access -- everything has the same index + | - speed-up modification -- just mirror all the operations + | - searching in more than one place + | + | introduce: + | - conversion on import, export and crop + | need to clear / insert all the nulls + | + | candidates: + | - marks + | - bookmarks + | - tags [X] crop/filter/search dialog... | make a number of fields each accepting a filter -- string/regexp [X] Q: how do we mark unsorted sections in base ribbon after aligning? diff --git a/ui/compatibility.js b/ui/compatibility.js index 5f4f514b..070f74a6 100755 --- a/ui/compatibility.js +++ b/ui/compatibility.js @@ -364,14 +364,14 @@ if(window.CEF_dumpJSON != null){ window.makeImagesPreviewsQ = function(gids, sizes, mode){ gids = gids == null ? getClosestGIDs() : gids - var queue = getWorkerQueue('preview_generator') + var queue = getWorkerQueue('Generate previews', 4) // attach the workers to the queue... $.each(gids, function(_, gid){ - queue.enqueue(null, makeImagePreviews, gid, sizes, mode) + queue.enqueue(makeImagePreviews, gid, sizes, mode) // XXX do we need to report seporate previews??? //.progress(function(state){ queue.notify(state) }) - .always(function(){ queue.notify(gid, 'done') }) + .always(function(){ console.log(gid, 'done') }) }) return queue diff --git a/ui/files.js b/ui/files.js index 3f83efc4..9f043e4e 100755 --- a/ui/files.js +++ b/ui/files.js @@ -684,7 +684,7 @@ function loadRawDir(path, no_preview_processing, prefix){ if(!no_preview_processing){ res.notify(prefix, 'Loading/Generating', 'Previews.') var p = makeImagesPreviewsQ() - .done(function(){ + .depleted(function(){ res.notify(prefix, 'Loaded', 'Previews.') }) @@ -844,7 +844,8 @@ function exportImagesTo(path, im_name, dir_name, size){ var z = (('10e' + (selection.length + '').length) * 1 + '').slice(2) // use an external pool... - var pool = makeDeferredPool() + //var pool = makeDeferredPool() + var pool = getWorkerQueue('Export previews', 64) .depleted(function(){ showStatusQ('Export: done.') res.resolve() @@ -929,7 +930,7 @@ function readImagesOrientation(gids, no_update_loaded){ function readImagesOrientationQ(gids, no_update_loaded){ gids = gids == null ? getClosestGIDs() : gids - var queue = getWorkerQueue('image_orientation_reader') + var queue = getWorkerQueue('Read images orientation', 4) var last = null @@ -963,7 +964,7 @@ function readImagesDates(images){ function readImagesDatesQ(images){ images = images == null ? IMAGES : images - var queue = getWorkerQueue('date_reader') + var queue = getWorkerQueue('Read images dates', 4) $.each(images, function(gid, img){ queue.enqueue(readImageDate, gid, images) @@ -1020,7 +1021,7 @@ function updateImagesGIDs(images, data){ function updateImagesGIDsQ(images, data){ images = images == null ? IMAGES : images - var queue = getWorkerQueue('gid_updater') + var queue = getWorkerQueue('Update GIDs', 4) $.each(images, function(_, key){ queue.enqueue(updateImageGID, key, images, data) diff --git a/ui/keybindings.js b/ui/keybindings.js index 6b89e253..3d307bf3 100755 --- a/ui/keybindings.js +++ b/ui/keybindings.js @@ -79,7 +79,10 @@ var KEYBOARD_CONFIG = { }, F5: doc('Full reload viewer', function(){ - reload() + killAllWorkers() + .done(function(){ + reload() + }) return false }), F12: doc('Show devTools', diff --git a/ui/layout.css b/ui/layout.css index 75a7bca4..690b7ff6 100755 --- a/ui/layout.css +++ b/ui/layout.css @@ -1141,6 +1141,64 @@ button:hover { font-size: 14px; opacity: 0.8; } +/*************************************************** Progress bars ***/ +progress { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 8px; +} +progress::-webkit-progress-bar { + background: transparent; + border: solid 1px gray; + border-radius: 3px; + padding: 1px; + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2) inset; +} +progress::-webkit-progress-value { + background: yellow; + border-radius: 2px; + box-shadow: -1px -1px 5px -2px rgba(0, 0, 0, 0.8) inset; +} +/* +progress:not(value)::-webkit-progress-bar { + background: transparent; + background-image: + -webkit-linear-gradient(-45deg, transparent 33%, rgba(0,0,0,0.2) 33%, + rgba(0,0,0,0.2) 66%, transparent 66%), + -webkit-linear-gradient(left, yellow, orange, yellow); + background-size: 50px 50px; + + -webkit-animation: animate-progress 5s linear infinite; + animation: animate-progress 5s linear infinite; +} + +@-webkit-keyframes animate-progress { + 100% { + background-position: -100% 0px; + } +} +@keyframes animate-progress { + 100% { + background-position: -100% 0px; + } +} +*/ +.progress-container { + position: absolute; +} +.progress-container:empty { + display: none; +} +.progress-bar { + color: silver; + font-size: 10px; + margin: 10px; +} +.progress-bar progress { + display: block; + width: 300px; +} /********************************************** Mode: single image ***/ .single-image-mode.viewer .ribbon { background-color: transparent; diff --git a/ui/layout.less b/ui/layout.less index 215a9104..af57cc55 100755 --- a/ui/layout.less +++ b/ui/layout.less @@ -1186,6 +1186,71 @@ button:hover { +/*************************************************** Progress bars ***/ +progress { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + height: 8px; +} +progress::-webkit-progress-bar { + background: transparent; + border: solid 1px gray; + border-radius: 3px; + padding: 1px; + box-shadow: 0px 2px 5px rgba(0,0,0,0.2) inset; +} +progress::-webkit-progress-value { + background: yellow; + border-radius: 2px; + box-shadow: -1px -1px 5px -2px rgba(0,0,0,0.8) inset; +} + +/* +progress:not(value)::-webkit-progress-bar { + background: transparent; + background-image: + -webkit-linear-gradient(-45deg, transparent 33%, rgba(0,0,0,0.2) 33%, + rgba(0,0,0,0.2) 66%, transparent 66%), + -webkit-linear-gradient(left, yellow, orange, yellow); + background-size: 50px 50px; + + -webkit-animation: animate-progress 5s linear infinite; + animation: animate-progress 5s linear infinite; +} + +@-webkit-keyframes animate-progress { + 100% { + background-position: -100% 0px; + } +} +@keyframes animate-progress { + 100% { + background-position: -100% 0px; + } +} +*/ + + +.progress-container { + position: absolute; +} +.progress-container:empty { + display: none; +} +.progress-bar { + color: silver; + font-size: 10px; + margin: 10px; +} +.progress-bar progress { + display: block; + width: 300px; +} + + + /********************************************** Mode: single image ***/ .single-image-mode.viewer .ribbon { diff --git a/ui/lib/jli.js b/ui/lib/jli.js index 05432979..1315db9b 100755 --- a/ui/lib/jli.js +++ b/ui/lib/jli.js @@ -628,7 +628,6 @@ jQuery.fn.sortChildren = function(func){ // Register a progress handler. // The handler is called after each worker is done and will get // passed: -// - pool // - workers done count // - workers total count // NOTE: the total number of workers can change as new workers @@ -638,7 +637,6 @@ jQuery.fn.sortChildren = function(func){ // Register a worker fail handler. // The handler is called when a worker goes into the fail state. // This will get passed: -// - pool // - workers done count // - workers total count // NOTE: this will not stop the execution of other handlers. @@ -698,7 +696,7 @@ function makeDeferredPool(size, paused){ var i = pool.indexOf(worker) Pool._progress_handlers.forEach(function(func){ - func(worker, pool.length - pool.len(), pool.length + queue.length) + func(pool.length - pool.len(), pool.length + queue.length) }) // remove self from queue... @@ -715,7 +713,7 @@ function makeDeferredPool(size, paused){ // if pool is empty fire the pause event... if(pool.len() == 0){ Pool._pause_handlers.forEach(function(func){ - func(that) + func() }) } return @@ -730,12 +728,13 @@ function makeDeferredPool(size, paused){ // empty queue AND empty pool mean we are done... } else if(pool.len() == 0){ + var l = pool.length // NOTE: potential race condition -- something can be // pushed to pool just before it's "compacted"... pool.length = 0 that._deplete_handlers.forEach(function(func){ - func(that) + func(l) }) } @@ -744,7 +743,7 @@ function makeDeferredPool(size, paused){ }) .fail(function(){ Pool._fail_handlers.forEach(function(func){ - func(that, pool.length - pool.len(), pool.length + queue.length) + func(pool.length - pool.len(), pool.length + queue.length) }) deferred.reject.apply(deferred, arguments) }) diff --git a/ui/workers.js b/ui/workers.js index 2b4b5c4f..e6ac6a99 100755 --- a/ui/workers.js +++ b/ui/workers.js @@ -16,16 +16,61 @@ var WORKERS = {} // get/create a named worker queue... // -// XXX rename this to task-related.... (???) -function getWorkerQueue(name, pool_size, no_auto_start){ +// XXX rename this to something task-related.... (???) +function getWorkerQueue(name, pool_size, no_auto_start, no_progress){ // XXX 1 is the default for compatibility... pool_size = pool_size == null ? 1 : pool_size + // XXX experimental -- STUB... + if(!no_progress){ + var container = $('.progress-container') + if(container.length == 0){ + container = $('
') + .appendTo($('.viewer')) + } + var progress = $('') + .appendTo(container) + var progress_state = $('') + .appendTo(progress) + var progress_bar = $('') + .appendTo(progress) + + // XXX for some reason without this, here the progress handlers + // later lose context... + progress = $(progress[0]) + progress_bar = $(progress_bar[0]) + } + // create a new worker queue... if(WORKERS[name] == null){ var queue = makeDeferredPool(pool_size, no_auto_start) WORKERS[name] = queue + // XXX experimental... + if(!no_progress){ + queue + .progress(function(done, total){ + progress_bar + .attr({ + value: done, + max: total + }) + progress_state + .text(' ('+done+' of '+total+')') + }) + .depleted(function(done){ + progress_bar + .attr('value', done) + progress_state + .text(' (done)') + + setTimeout(function(){ + progress + .remove() + }, 1500) + }) + } + // return existing worker queue... } else { var queue = WORKERS[name] @@ -35,15 +80,35 @@ function getWorkerQueue(name, pool_size, no_auto_start){ } -// kill all worker queues... +// Kill all worker queues... +// +// Returns a deffered object that will get resolved when all workers are +// actually stopped... function killAllWorkers(){ - for(var k in WORKERS){ + var res = $.Deferred() + var w = [] + Object.keys(WORKERS).forEach(function(k){ if(WORKERS[k].isRunning()){ - console.log('Worker: Stopped:', k) + var wd = $.Deferred() + w.push(wd) + WORKERS[k] + .depleted(function(){ + console.log('Worker: Stopped:', k) + wd.resolve() + }) } - WORKERS[k].dropQueue() - } + WORKERS[k] + .dropQueue() + }) WORKERS = {} + + $.when.apply(null, w) + .done(function(){ + console.log('Worker: All workers stopped.') + res.resolve() + }) + + return res }