X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/e38ac44d14e9006c24c93bca9de1ee299b16d367..526f3e54133c863b78314924a485d327eb9ba392:/apps/workbench/app/assets/javascripts/models/loader.js diff --git a/apps/workbench/app/assets/javascripts/models/loader.js b/apps/workbench/app/assets/javascripts/models/loader.js index 1b446704c4..0b29de68de 100644 --- a/apps/workbench/app/assets/javascripts/models/loader.js +++ b/apps/workbench/app/assets/javascripts/models/loader.js @@ -10,9 +10,10 @@ // response. loadFunc() must retrieve results in "modified_at desc" // order. // -// done is true if there are no more pages to load. -// -// loading is true if a network request is in progress. +// state is: +// * 'loading' if a network request is in progress; +// * 'done' if there are no more items to load; +// * 'ready' otherwise. // // items is a stream that resolves to an array of all items retrieved so far. // @@ -20,30 +21,34 @@ window.MultipageLoader = function(config) { var loader = this Object.assign(loader, config, { - done: false, - loading: false, - items: m.stream(), + state: 'ready', + DONE: 'done', + LOADING: 'loading', + READY: 'ready', + + items: m.stream([]), thresholdItem: null, loadMore: function() { - if (loader.done || loader.loading) + if (loader.state == loader.DONE || loader.state == loader.LOADING) return var filters = loader.thresholdItem ? [ ["modified_at", "<=", loader.thresholdItem.modified_at], ["uuid", "!=", loader.thresholdItem.uuid], ] : [] - loader.loading = true + loader.state = loader.LOADING loader.loadFunc(filters).then(function(resp) { - var items = loader.items() || [] + var items = loader.items() Array.prototype.push.apply(items, resp.items) - if (resp.items.length == 0) - loader.done = true - else + if (resp.items.length == 0) { + loader.state = loader.DONE + } else { loader.thresholdItem = resp.items[resp.items.length-1] - loader.loading = false + loader.state = loader.READY + } loader.items(items) }).catch(function(err) { loader.err = err - loader.loading = false + loader.state = loader.READY }) }, }) @@ -60,22 +65,25 @@ window.MergingLoader = function(config) { var loader = this Object.assign(loader, config, { // Sorted items ready to display, merged from all children. - items: m.stream(), - done: false, - loading: false, + items: m.stream([]), + state: 'ready', + DONE: 'done', + LOADING: 'loading', + READY: 'ready', loadable: function() { // Return an array of children that we could call - // loadMore() on. Update loader.done and loader.loading. - loader.done = true - loader.loading = false + // loadMore() on. Update loader.state. + loader.state = loader.DONE return loader.children.filter(function(child) { - if (child.done) + if (child.state == child.DONE) + return false + if (child.state == child.LOADING) { + loader.state = loader.LOADING return false - loader.done = false - if (!child.loading) - return true - loader.loading = true - return false + } + if (loader.state == loader.DONE) + loader.state = loader.READY + return true }) }, loadMore: function() { @@ -83,31 +91,48 @@ window.MergingLoader = function(config) { // lowWaterMark. loader.loadable().map(function(child) { if (child.items().length - child.itemsDisplayed < loader.lowWaterMark) { - loader.loading = true + loader.state = loader.LOADING child.loadMore() } }) }, mergeItems: function() { - // cutoff is the topmost (recent) of {bottom (oldest) entry of - // any child that still has more pages left to fetch} + // We want to avoid moving items around on the screen once + // they're displayed. + // + // To this end, here we find the last safely displayable + // item ("cutoff") by getting the last item from each + // unfinished child, and taking the topmost (most recent) + // one of those. + // + // (If we were to display an item below that cutoff, the + // next page of results from an unfinished child could + // include items that get inserted above the cutoff, + // causing the cutoff item to move down.) var cutoff + var cutoffUnknown = false loader.children.forEach(function(child) { + if (child.state == child.DONE) + return var items = child.items() - if (items.length == 0 || child.done) + if (items.length == 0) { + // No idea what's coming in the next page. + cutoffUnknown = true return + } var last = items[items.length-1].modified_at if (!cutoff || cutoff < last) cutoff = last }) + if (cutoffUnknown) + return var combined = [] loader.children.forEach(function(child) { child.itemsDisplayed = 0 child.items().every(function(item) { if (cutoff && item.modified_at < cutoff) - // Some other children haven't caught up to this - // point, so don't display this item or anything - // after it. + // Don't display this item or anything after + // it (see "cutoff" comment above). return false combined.push(item) child.itemsDisplayed++