1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 window.SearchResultsTable = {
6 maybeLoadMore: function(dom) {
7 var loader = this.loader
8 if (loader.state != loader.READY)
9 // Can't start getting more items anyway: no point in
10 // checking anything else.
12 var contentRect = dom.getBoundingClientRect()
13 var scroller = window // TODO: use dom's nearest ancestor with scrollbars
14 if (contentRect.bottom < 2 * scroller.innerHeight) {
15 // We have less than 1 page worth of content available
16 // below the visible area. Load more.
18 // Indicate loading is in progress.
19 window.requestAnimationFrame(m.redraw)
22 oncreate: function(vnode) {
23 vnode.state.maybeLoadMore = vnode.state.maybeLoadMore.bind(vnode.state, vnode.dom)
24 window.addEventListener('scroll', vnode.state.maybeLoadMore)
25 window.addEventListener('resize', vnode.state.maybeLoadMore)
26 vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
27 vnode.state.loader = vnode.attrs.loader
28 vnode.state.onupdate(vnode)
30 onupdate: function(vnode) {
31 vnode.state.loader = vnode.attrs.loader
33 onremove: function(vnode) {
34 window.clearInterval(vnode.state.timer)
35 window.removeEventListener('scroll', vnode.state.maybeLoadMore)
36 window.removeEventListener('resize', vnode.state.maybeLoadMore)
38 view: function(vnode) {
39 var loader = vnode.attrs.loader
41 collections: m('i.fa.fa-fw.fa-archive'),
42 projects: m('i.fa.fa-fw.fa-folder'),
44 var db = new SessionDB()
45 var sessions = db.loadActive()
46 return m('table.table.table-condensed', [
51 m('th', 'last modified'),
54 loader.items().map(function(item) {
55 var session = sessions[item.uuid.slice(0,5)]
57 // Add the salted token to search result links from federated
59 if (!session.isFromRails && session.token.indexOf('v2/') == 0) {
60 tokenParam = session.token
64 action: item.workbenchBaseURL() + '/' + item.objectType.wb_path + '/' + item.uuid,
68 m('input[type=hidden][name=api_token]', {value: tokenParam}),
69 item.workbenchBaseURL() &&
70 m('button.btn.btn-xs.btn-default[type=submit]', {
71 'data-original-title': 'show '+item.objectType.description,
72 'data-placement': 'top',
73 'data-toggle': 'tooltip',
74 // Bootstrap's tooltip feature
75 oncreate: function(vnode) { $(vnode.dom).tooltip() },
76 }, iconsMap[item.objectType.wb_path]),
78 m('td.arvados-uuid', item.uuid),
79 m('td', item.name || '(unnamed)'),
80 m('td', m(LocalizedDateTime, {parse: item.modified_at})),
84 loader.state == loader.DONE ? null : m('tfoot', m('tr', [
85 m('th[colspan=4]', m('button.btn.btn-xs', {
86 className: loader.state == loader.LOADING ? 'btn-default' : 'btn-primary',
93 disabled: loader.state == loader.LOADING,
98 }, loader.state == loader.LOADING ? '(loading)' : 'Load more')),
105 oninit: function(vnode) {
106 vnode.state.sessionDB = new SessionDB()
107 vnode.state.sessionDB.autoRedirectToHomeCluster('/search')
108 vnode.state.searchEntered = m.stream()
109 vnode.state.searchActive = m.stream()
110 // When searchActive changes (e.g., when restoring state
111 // after navigation), update the text field too.
112 vnode.state.searchActive.map(vnode.state.searchEntered)
113 // When searchActive changes, create a new loader that filters
114 // with the given search term.
115 vnode.state.searchActive.map(function(q) {
116 var sessions = vnode.state.sessionDB.loadActive()
117 vnode.state.loader = new MergingLoader({
118 children: Object.keys(sessions).map(function(key) {
119 var session = sessions[key]
120 var workbenchBaseURL = function() {
121 return vnode.state.sessionDB.workbenchBaseURL(session)
123 var searchable_objects = [
126 api_path: 'arvados/v1/groups',
127 filters: [['group_class', '=', 'project']],
128 description: 'project',
132 api_path: 'arvados/v1/groups',
133 filters: [['group_class', '=', 'filter']],
134 description: 'project',
137 wb_path: 'collections',
138 api_path: 'arvados/v1/collections',
140 description: 'collection',
143 return new MergingLoader({
145 // For every session, search for every object type
146 children: searchable_objects.map(function(obj_type) {
147 return new MultipageLoader({
149 loadFunc: function(filters) {
150 // Apply additional type dependant filters
151 filters = filters.concat(obj_type.filters).concat(ilike_filters(q))
152 return vnode.state.sessionDB.request(session, obj_type.api_path, {
154 filters: JSON.stringify(filters),
157 }).then(function(resp) {
158 resp.items.map(function(item) {
159 item.workbenchBaseURL = workbenchBaseURL
160 item.objectType = obj_type
172 view: function(vnode) {
174 onsubmit: function() {
175 vnode.state.searchActive(vnode.state.searchEntered())
176 vnode.state.forgetSavedHeight = true
182 currentState: vnode.state.searchActive,
183 forgetSavedHeight: vnode.state.forgetSavedHeight,
184 saveBodyHeight: true,
186 vnode.state.loader && [
190 m('input#search.form-control[placeholder=Search collections and projects]', {
191 oninput: m.withAttr('value', vnode.state.searchEntered),
192 value: vnode.state.searchEntered(),
194 m('.input-group-btn', [
195 m('input.btn.btn-primary[type=submit][value="Search"]'),
201 vnode.state.loader.children.length == 0
202 ? m('span.label.label-xs.label-danger', 'none')
203 : vnode.state.loader.children.map(function(child) {
204 return [m('span.label.label-xs', {
205 className: child.state == child.LOADING ? 'label-warning' : 'label-success',
206 }, child.sessionKey), ' ']
209 m('a[href="/sessions"]', 'Add/remove sites'),
212 m(SearchResultsTable, {
213 loader: vnode.state.loader,