// SPDX-License-Identifier: AGPL-3.0
window.components = window.components || {}
-window.components.collection_table_narrow = {
+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
+ // checking anything else.
+ return
+ var contentRect = dom.getBoundingClientRect()
+ var scroller = window // TODO: use dom's nearest ancestor with scrollbars
+ if (contentRect.bottom < 2 * scroller.innerHeight) {
+ // We have less than 1 page worth of content available
+ // below the visible area. Load more.
+ loader.loadMore()
+ // Indicate loading is in progress.
+ window.requestAnimationFrame(m.redraw)
+ }
+ },
+ oncreate: function(vnode) {
+ vnode.state.maybeLoadMore = vnode.state.maybeLoadMore.bind(vnode.state, vnode.dom)
+ window.addEventListener('scroll', vnode.state.maybeLoadMore)
+ window.addEventListener('resize', vnode.state.maybeLoadMore)
+ vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
+ vnode.state.onupdate(vnode)
+ },
+ onupdate: function(vnode) {
+ vnode.state.loader = vnode.attrs.loader
+ },
+ onremove: function(vnode) {
+ window.clearInterval(vnode.state.timer)
+ window.removeEventListener('scroll', vnode.state.maybeLoadMore)
+ window.removeEventListener('resize', vnode.state.maybeLoadMore)
+ },
view: function(vnode) {
return m('table.table.table-condensed', [
- m('thead', m('tr', m('th', vnode.attrs.key))),
+ m('thead', m('tr', [
+ m('th'),
+ m('th', 'uuid'),
+ m('th', 'name'),
+ m('th', 'last modified'),
+ ])),
m('tbody', [
- vnode.attrs.items().map(function(item) {
+ vnode.attrs.loader.displayable.map(function(item) {
return m('tr', [
- m('td', [
- m('a', {href: '/collections/'+item.uuid}, item.name || '(unnamed)'),
- m('br'),
- item.modified_at,
- ]),
+ 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),
+ m('td', item.name || '(unnamed)'),
+ m('td', m(window.components.datetime, {parse: item.modified_at})),
])
}),
]),
+ 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',
+ style: {
+ display: 'block',
+ width: '12em',
+ marginLeft: 'auto',
+ marginRight: 'auto',
+ },
+ disabled: !vnode.attrs.loader.loadMore,
+ onclick: function() {
+ vnode.attrs.loader.loadMore()
+ return false
+ },
+ }, vnode.attrs.loader.loadMore ? 'Load more' : '(loading)')),
+ ])),
])
},
}
vnode.state.sessionDB = new window.models.SessionDB()
vnode.state.searchEntered = m.stream('')
vnode.state.searchStart = m.stream('')
- vnode.state.items = {}
vnode.state.searchStart.map(function(q) {
- var sessions = vnode.state.sessionDB.loadAll()
- var cookie = (new Date()).getTime()
- vnode.state.cookie = cookie
- Object.keys(sessions).map(function(key) {
- if (!vnode.state.items[key])
- vnode.state.items[key] = m.stream([])
- vnode.state.items[key].dirty = true
- vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
- data: {
- filters: JSON.stringify(!q ? [] : [['any', '@@', q+':*']]),
- },
- }).then(function(resp) {
- if (cookie !== vnode.state.cookie)
- // a newer query is in progress; ignore this result.
- return
- vnode.state.items[key](resp.items)
- vnode.state.items[key].dirty = false
- })
+ vnode.state.loader = new window.models.MultisiteLoader({
+ loadFunc: function(session, filters) {
+ if (q)
+ filters.push(['any', '@@', q+':*'])
+ return vnode.state.sessionDB.request(session, 'arvados/v1/collections', {
+ data: {
+ filters: JSON.stringify(filters),
+ count: 'none',
+ },
+ })
+ },
+ sessionDB: vnode.state.sessionDB,
})
})
},
view: function(vnode) {
- var items = vnode.state.items
+ var sessions = vnode.state.sessionDB.loadAll()
return m('form', {
onsubmit: function() {
vnode.state.searchStart(vnode.state.searchEntered())
m('.col-md-6', [
m('.input-group', [
m('input#search.form-control[placeholder=Search]', {
- oninput: m.withAttr('value', debounce(200, vnode.state.searchEntered)),
+ oninput: m.withAttr('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(items).length == 0
+ Object.keys(sessions).length == 0
? m('span.label.label-xs.label-danger', 'none')
- : Object.keys(items).sort().map(function(key) {
- return [m('span.label.label-xs.label-info', key), ' ']
+ : Object.keys(sessions).sort().map(function(key) {
+ return [m('span.label.label-xs', {
+ className: vnode.state.loader.pagers[key].items() ? 'label-info' : 'label-default',
+ }, key), ' ']
}),
' ',
m('a[href="/sessions"]', 'Add/remove sites'),
]),
]),
- m('.row', Object.keys(items).sort().map(function(key) {
- return m('.col-md-3', {key: key, style: {
- opacity: items[key].dirty ? 0.5 : 1,
- }}, [
- m(window.components.collection_table_narrow, {
- key: key,
- items: items[key],
- }),
- ])
- })),
+ m(window.components.collection_table, {
+ loader: vnode.state.loader,
+ }),
])
},
}
-
-function debounce(t, f) {
- // Return a new function that waits until t milliseconds have
- // passed since it was last called, then calls f with its most
- // recent arguments.
- var this_was = this
- var pending
- return function() {
- var args = arguments
- if (pending) {
- console.log("debounce!")
- window.clearTimeout(pending)
- }
- pending = window.setTimeout(function() {
- pending = undefined
- f.apply(this_was, args)
- }, t)
- }
-}