[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