[ARVADOS] updated: fb5ecab97a8458028c875201def999a50c437621

Git user git at public.curoverse.com
Sun Aug 13 13:55:17 EDT 2017


Summary of changes:
 .../assets/javascripts/components/collections.js   |  88 ++++++----
 .../assets/javascripts/components/save_state.js    |  47 +++++
 .../app/assets/javascripts/components/sessions.js  |   2 +-
 .../app/assets/javascripts/models/loader.js        | 195 ++++++++++++---------
 4 files changed, 216 insertions(+), 116 deletions(-)
 create mode 100644 apps/workbench/app/assets/javascripts/components/save_state.js

       via  fb5ecab97a8458028c875201def999a50c437621 (commit)
       via  77a65bed2b82ae59ba595495670fb03146291d16 (commit)
       via  5b246542881dddd22d5343174f4c7b8bca0d55aa (commit)
      from  888e08c4bd321745607ca35cd71fa6c53ece0405 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit fb5ecab97a8458028c875201def999a50c437621
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun Aug 13 13:53:06 2017 -0400

    12033: MultipageLoader and MultisiteLoader offer the same interface.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>

diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 98c2ab0..8d6e527 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -6,8 +6,8 @@ window.components = window.components || {}
 window.components.collection_table = {
     maybeLoadMore: function(dom) {
         var loader = this.loader
-        if (loader.done || !loader.loadMore)
-            // Can't load more content anyway: no point in
+        if (loader.done || loader.loading)
+            // Can't start getting more items anyway: no point in
             // checking anything else.
             return
         var contentRect = dom.getBoundingClientRect()
@@ -45,7 +45,7 @@ window.components.collection_table = {
                 m('th', 'last modified'),
             ])),
             m('tbody', [
-                vnode.attrs.loader.displayable.map(function(item) {
+                vnode.attrs.loader.items() && vnode.attrs.loader.items().map(function(item) {
                     return m('tr', [
                         m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'collections/'+item.uuid}, 'Show')),
                         m('td.arvados-uuid', item.uuid),
@@ -56,19 +56,19 @@ window.components.collection_table = {
             ]),
             m('tfoot', m('tr', [
                 vnode.attrs.loader.done ? null : m('th[colspan=4]', m('button.btn.btn-xs', {
-                    className: vnode.attrs.loader.loadMore ? 'btn-primary' : 'btn-default',
+                    className: vnode.attrs.loader.loading ? 'btn-default' : 'btn-primary',
                     style: {
                         display: 'block',
                         width: '12em',
                         marginLeft: 'auto',
                         marginRight: 'auto',
                     },
-                    disabled: !vnode.attrs.loader.loadMore,
+                    disabled: vnode.attrs.loader.loading,
                     onclick: function() {
                         vnode.attrs.loader.loadMore()
                         return false
                     },
-                }, vnode.attrs.loader.loadMore ? 'Load more' : '(loading)')),
+                }, vnode.attrs.loader.loading ? '(loading)' : 'Load more')),
             ])),
         ])
     },
@@ -134,8 +134,8 @@ window.components.collection_search = {
                             ? m('span.label.label-xs.label-danger', 'none')
                             : Object.keys(sessions).sort().map(function(key) {
                                 return [m('span.label.label-xs', {
-                                    className: !vnode.state.loader.pagers[key] ? 'label-default' :
-                                        vnode.state.loader.pagers[key].items() ? 'label-success' :
+                                    className: !vnode.state.loader.children[key] ? 'label-default' :
+                                        vnode.state.loader.children[key].items() ? 'label-success' :
                                         'label-warning',
                                 }, key), ' ']
                             }),
diff --git a/apps/workbench/app/assets/javascripts/models/loader.js b/apps/workbench/app/assets/javascripts/models/loader.js
index 17b2ad5..53bf76b 100644
--- a/apps/workbench/app/assets/javascripts/models/loader.js
+++ b/apps/workbench/app/assets/javascripts/models/loader.js
@@ -2,33 +2,53 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+// MultipageLoader retrieves a multi-page result set from the
+// server. The constructor initiates the first page load.
+//
+// config.loadFunc is a function that accepts an array of
+// paging-related filters, and returns a promise for the API
+// 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.
+//
+// items is a stream that resolves to an array of all items retrieved so far.
+//
+// loadMore() loads the next page, if any.
 window.models = window.models || {}
-window.models.Pager = function(loadFunc) {
-    // loadFunc(filters) must return a promise for a page of results.
-    var pager = this
-    Object.assign(pager, {
+window.models.MultipageLoader = function(config) {
+    var loader = this
+    Object.assign(loader, config, {
         done: false,
+        loading: false,
         items: m.stream(),
         thresholdItem: null,
         loadMore: function() {
-            // Get the next page, if there are any more items to get.
-            if (pager.done)
+            if (loader.done || loader.loading)
                 return
-            var filters = pager.thresholdItem ? [
-                ["modified_at", "<=", pager.thresholdItem.modified_at],
-                ["uuid", "!=", pager.thresholdItem.uuid],
+            var filters = loader.thresholdItem ? [
+                ["modified_at", "<=", loader.thresholdItem.modified_at],
+                ["uuid", "!=", loader.thresholdItem.uuid],
             ] : []
-            loadFunc(filters).then(function(resp) {
-                var items = pager.items() || []
+            loader.loading = true
+            loader.loadFunc(filters).then(function(resp) {
+                var items = loader.items() || []
                 Array.prototype.push.apply(items, resp.items)
                 if (resp.items.length == 0)
-                    pager.done = true
+                    loader.done = true
                 else
-                    pager.thresholdItem = resp.items[resp.items.length-1]
-                pager.items(items)
+                    loader.thresholdItem = resp.items[resp.items.length-1]
+                loader.loading = false
+                loader.items(items)
+            }).catch(function(err) {
+                loader.err = err
+                loader.loading = false
             })
         },
     })
+    loader.loadMore()
 }
 
 // MultisiteLoader loads pages of results from multiple API sessions
@@ -45,84 +65,101 @@ window.models.Pager = function(loadFunc) {
 // function that starts loading more results.
 //
 // loadFunc() must retrieve results in "modified_at desc" order.
+//
+// (TODO? This could split into two parts: "make a loader for each
+// session, attaching session to each returned item", and "merge items
+// from N loaders".)
 window.models = window.models || {}
 window.models.MultisiteLoader = function(config) {
     var loader = this
     if (!(config.loadFunc && config.sessionDB))
         throw new Error("MultisiteLoader constructor requires loadFunc and sessionDB")
     Object.assign(loader, config, {
-        // Sorted items ready to display, merged from all pagers.
-        displayable: [],
+        sessions: config.sessionDB.loadActive(),
+        // Sorted items ready to display, merged from all children.
+        items: m.stream(),
         done: false,
-        pagers: {},
-        loadMore: false,
+        children: {},
+        loading: false,
+        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
+            return Object.keys(loader.children)
+                .map(function(key) { return loader.children[key] })
+                .filter(function(child) {
+                    if (child.done)
+                        return false
+                    loader.done = false
+                    if (!child.loading)
+                        return true
+                    loader.loading = true
+                    return false
+                })
+        },
+        loadMore: function() {
+            // Call loadMore() on children that have reached
+            // lowWaterMark.
+            loader.loadable().map(function(child) {
+                if (child.items().length - child.itemsDisplayed < loader.lowWaterMark) {
+                    loader.loading = true
+                    child.loadMore()
+                }
+            })
+        },
+        mergeItems: function() {
+            var keys = Object.keys(loader.sessions)
+            // cutoff is the topmost (recent) of {bottom (oldest) entry of
+            // any child that still has more pages left to fetch}
+            var cutoff
+            keys.forEach(function(key) {
+                var child = loader.children[key]
+                var items = child.items()
+                if (items.length == 0 || child.done)
+                    return
+                var last = items[items.length-1].modified_at
+                if (!cutoff || cutoff < last)
+                    cutoff = last
+            })
+            var combined = []
+            keys.forEach(function(key) {
+                var child = loader.children[key]
+                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.
+                        return false
+                    item.session = loader.sessions[key]
+                    combined.push(item)
+                    child.itemsDisplayed++
+                    return true // continue
+                })
+            })
+            loader.items(combined.sort(function(a, b) {
+                return a.modified_at < b.modified_at ? 1 : -1
+            }))
+        },
         // Number of undisplayed items to keep on hand for each result
         // set. When hitting "load more", if a result set already has
         // this many additional results available, we don't bother
         // fetching a new page. This is the _minimum_ number of rows
-        // that will be added to loader.displayable in each "load
-        // more" event (except for the case where all items are
-        // displayed).
+        // that will be added to loader.items in each "load more"
+        // event (except for the case where all items are displayed).
         lowWaterMark: 23,
     })
-    var sessions = loader.sessionDB.loadActive()
-    m.stream.merge(Object.keys(sessions).map(function(key) {
-        var pager = new window.models.Pager(loader.loadFunc.bind(null, sessions[key]))
-        loader.pagers[key] = pager
-        pager.loadMore()
-        // Resolve the stream with the session key when the results
-        // arrive.
-        return pager.items.map(function() { return key })
-    })).map(function(keys) {
-        // Top (most recent) of {bottom (oldest) entry of any pager
-        // that still has more pages left to fetch}
-        var cutoff
-        keys.forEach(function(key) {
-            var pager = loader.pagers[key]
-            var items = pager.items()
-            if (items.length == 0 || pager.done)
-                return
-            var last = items[items.length-1].modified_at
-            if (!cutoff || cutoff < last)
-                cutoff = last
+    var childrenItems = Object.keys(loader.sessions).map(function(key) {
+        var child = new window.models.MultipageLoader({
+            loadFunc: loader.loadFunc.bind(null, loader.sessions[key]),
         })
-        var combined = []
-        keys.forEach(function(key) {
-            var pager = loader.pagers[key]
-            pager.itemsDisplayed = 0
-            pager.items().every(function(item) {
-                if (cutoff && item.modified_at < cutoff)
-                    // Some other pagers haven't caught up to this
-                    // point, so don't display this item or anything
-                    // after it.
-                    return false
-                item.session = sessions[key]
-                combined.push(item)
-                pager.itemsDisplayed++
-                return true // continue
-            })
-        })
-        loader.displayable = combined.sort(function(a, b) {
-            return a.modified_at < b.modified_at ? 1 : -1
-        })
-        // Make a new loadMore function that hits the pagers (if
-        // necessary according to lowWaterMark)... or set
-        // loader.loadMore to false if there is nothing left to fetch.
-        var loadable = []
-        Object.keys(loader.pagers).map(function(key) {
-            if (!loader.pagers[key].done)
-                loadable.push(loader.pagers[key])
-        })
-        if (loadable.length == 0) {
-            loader.done = true
-            loader.loadMore = false
-        } else
-            loader.loadMore = function() {
-                loader.loadMore = false
-                loadable.map(function(pager) {
-                    if (pager.items().length - pager.itemsDisplayed < loader.lowWaterMark)
-                        pager.loadMore()
-                })
-            }
+        loader.children[key] = child
+        // Resolve with the session key whenever results arrive for
+        // that session.
+        return child.items
     })
+    var childrenReady = m.stream.merge(childrenItems)
+    childrenReady.map(loader.loadable)
+    childrenReady.map(loader.mergeItems)
 }

commit 77a65bed2b82ae59ba595495670fb03146291d16
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun Aug 13 01:52:49 2017 -0400

    12033: Fix typo in selector.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>

diff --git a/apps/workbench/app/assets/javascripts/components/sessions.js b/apps/workbench/app/assets/javascripts/components/sessions.js
index 19a481d..97af8b2 100644
--- a/apps/workbench/app/assets/javascripts/components/sessions.js
+++ b/apps/workbench/app/assets/javascripts/components/sessions.js
@@ -17,7 +17,7 @@ window.components.sessions = {
     view: function(vnode) {
         var db = vnode.state.db
         var sessions = db.loadAll()
-        return m('container', [
+        return m('.container', [
             m('table.table.table-condensed.table-hover', [
                 m('thead', m('tr', [
                     m('th', 'status'),

commit 5b246542881dddd22d5343174f4c7b8bca0d55aa
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun Aug 13 01:50:55 2017 -0400

    12033: Restore search term and scroll position after navigation.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>

diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 9e1db75..98c2ab0 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -25,6 +25,7 @@ window.components.collection_table = {
         window.addEventListener('scroll', vnode.state.maybeLoadMore)
         window.addEventListener('resize', vnode.state.maybeLoadMore)
         vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
+        vnode.state.loader = vnode.attrs.loader
         vnode.state.onupdate(vnode)
     },
     onupdate: function(vnode) {
@@ -76,9 +77,14 @@ window.components.collection_table = {
 window.components.collection_search = {
     oninit: function(vnode) {
         vnode.state.sessionDB = new window.models.SessionDB()
-        vnode.state.searchEntered = m.stream('')
-        vnode.state.searchStart = m.stream('')
-        vnode.state.searchStart.map(function(q) {
+        vnode.state.searchEntered = m.stream()
+        vnode.state.searchActive = m.stream()
+        // When searchActive changes (e.g., when restoring state
+        // after navigation), update the text field too.
+        vnode.state.searchActive.map(vnode.state.searchEntered)
+        // When searchActive changes, create a new loader that filters
+        // with the given search term.
+        vnode.state.searchActive.map(function(q) {
             vnode.state.loader = new window.models.MultisiteLoader({
                 loadFunc: function(session, filters) {
                     if (q)
@@ -98,39 +104,49 @@ window.components.collection_search = {
         var sessions = vnode.state.sessionDB.loadAll()
         return m('form', {
             onsubmit: function() {
-                vnode.state.searchStart(vnode.state.searchEntered())
+                vnode.state.searchActive(vnode.state.searchEntered())
+                vnode.state.forgetSavedState = true
                 return false
             },
         }, [
-            m('.row', [
-                m('.col-md-6', [
-                    m('.input-group', [
-                        m('input#search.form-control[placeholder=Search]', {
-                            oninput: m.withAttr('value', vnode.state.searchEntered),
-                        }),
-                        m('.input-group-btn', [
-                            m('input.btn.btn-primary[type=submit][value="Search"]'),
+            m(window.components.save_state, {
+                defaultState: '',
+                currentState: vnode.state.searchActive,
+                forgetSavedState: vnode.state.forgetSavedState,
+                saveBodyHeight: true,
+            }),
+            vnode.state.loader && [
+                m('.row', [
+                    m('.col-md-6', [
+                        m('.input-group', [
+                            m('input#search.form-control[placeholder=Search]', {
+                                oninput: m.withAttr('value', vnode.state.searchEntered),
+                                value: vnode.state.searchEntered(),
+                            }),
+                            m('.input-group-btn', [
+                                m('input.btn.btn-primary[type=submit][value="Search"]'),
+                            ]),
                         ]),
                     ]),
+                    m('.col-md-6', [
+                        'Searching sites: ',
+                        Object.keys(sessions).length == 0
+                            ? m('span.label.label-xs.label-danger', 'none')
+                            : Object.keys(sessions).sort().map(function(key) {
+                                return [m('span.label.label-xs', {
+                                    className: !vnode.state.loader.pagers[key] ? 'label-default' :
+                                        vnode.state.loader.pagers[key].items() ? 'label-success' :
+                                        'label-warning',
+                                }, key), ' ']
+                            }),
+                        ' ',
+                        m('a[href="/sessions"]', 'Add/remove sites'),
+                    ]),
                 ]),
-                m('.col-md-6', [
-                    'Searching sites: ',
-                    Object.keys(sessions).length == 0
-                        ? m('span.label.label-xs.label-danger', 'none')
-                        : Object.keys(sessions).sort().map(function(key) {
-                            return [m('span.label.label-xs', {
-                                className: !vnode.state.loader.pagers[key] ? 'label-default' :
-                                    vnode.state.loader.pagers[key].items() ? 'label-success' :
-                                    'label-warning',
-                            }, key), ' ']
-                        }),
-                    ' ',
-                    m('a[href="/sessions"]', 'Add/remove sites'),
-                ]),
-            ]),
-            m(window.components.collection_table, {
-                loader: vnode.state.loader,
-            }),
+                m(window.components.collection_table, {
+                    loader: vnode.state.loader,
+                }),
+            ],
         ])
     },
 }
diff --git a/apps/workbench/app/assets/javascripts/components/save_state.js b/apps/workbench/app/assets/javascripts/components/save_state.js
new file mode 100644
index 0000000..021f7ff
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/components/save_state.js
@@ -0,0 +1,47 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.components = window.components || {}
+window.components.save_state = {
+    saveState: function() {
+        var state = history.state || {}
+        state.bodyHeight = window.getComputedStyle(document.body)['height']
+        state.currentState = this.currentState()
+        history.replaceState(state, '')
+    },
+    oninit: function(vnode) {
+        vnode.state.currentState = vnode.attrs.currentState
+        var hstate = history.state || {}
+
+        if (vnode.attrs.saveBodyHeight && hstate.bodyHeight) {
+            document.body.style['min-height'] = hstate.bodyHeight
+            delete hstate.bodyHeight
+        }
+
+        if (hstate.currentState) {
+            vnode.attrs.currentState(hstate.currentState)
+            delete hstate.currentState
+        } else {
+            vnode.attrs.currentState(vnode.attrs.defaultState)
+        }
+
+        history.replaceState(hstate, '')
+    },
+    oncreate: function(vnode) {
+        vnode.state.saveState = vnode.state.saveState.bind(vnode.state)
+        window.addEventListener('beforeunload', vnode.state.saveState)
+        vnode.state.onupdate(vnode)
+    },
+    onupdate: function(vnode) {
+        if (vnode.attrs.saveBodyHeight && vnode.attrs.forgetSavedState) {
+            document.body.style['min-height'] = null
+        }
+    },
+    onremove: function(vnode) {
+        window.removeEventListener('beforeunload', vnode.state.saveState)
+    },
+    view: function(vnode) {
+        return null
+    },
+}

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list