diff --git a/frontend/package.json b/frontend/package.json
index 4abf4ac..199ef17 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,6 +2,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.12.0",
"bootstrap": "^4.4.1",
+ "filesize": "^6.0.1",
"jquery": "^3.4.1",
"js-uuid": "0.0.6",
"linkstate": "^1.1.1",
diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
index b250a14..e98dca8 100755
--- a/frontend/rollup.config.js
+++ b/frontend/rollup.config.js
@@ -42,6 +42,7 @@ export default {
'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff': 'dist/webfonts/fa-brands-400.woff',
'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2',
'node_modules/js-uuid/js-uuid.js': 'dist/js/js-uuid.js',
+ 'node_modules/filesize/lib/filesize.js': 'dist/js/filesize.js',
verbose: true
}),
buble({jsx: 'h'}),
diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html
index 79f83d9..114c934 100755
--- a/frontend/src/html/index.html
+++ b/frontend/src/html/index.html
@@ -8,6 +8,7 @@
+
diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js
new file mode 100644
index 0000000..993839c
--- /dev/null
+++ b/frontend/src/js/component/wb-collection-listing.js
@@ -0,0 +1,128 @@
+import { h, Component } from 'preact';
+import { route } from 'preact-router';
+import makeArvadosRequest from 'make-arvados-request';
+import WBTable from 'wb-table';
+import WBPagination from 'wb-pagination';
+import urlForObject from 'url-for-object';
+import arvadosTypeName from 'arvados-type-name';
+import arvadosObjectName from 'arvados-object-name';
+
+class WBCollectionListing extends Component {
+
+ constructor(...args) {
+ super(...args);
+ this.state.rows = [];
+ this.state.numPages = 0;
+ }
+
+ componentDidMount() {
+ this.fetchItems();
+ }
+
+ prepareRows(items, ownerLookup) {
+ return items.map(item => [
+ (),
+ item['description'],
+ (
+
+
{ item.owner_uuid }
+
),
+ item['file_count'],
+ filesize(item['file_size_total'])
+ ]);
+ }
+
+ fetchItems() {
+ let { arvHost, arvToken } = this.props.app.state;
+
+ let { activePage, itemsPerPage, ownerUuid } = this.props;
+
+ let filters = [];
+
+ if (ownerUuid)
+ filters.push([ 'owner_uuid', '=', ownerUuid ]);
+
+ let prom = makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)) +
+ '&limit=' + encodeURIComponent(itemsPerPage) +
+ '&offset=' + encodeURIComponent(itemsPerPage * activePage));
+
+ let collections;
+ let numPages
+
+ prom = prom.then(xhr => {
+ collections = xhr.response['items'];
+ numPages = Math.ceil(xhr.response['items_available'] / xhr.response['limit']);
+
+ let owners = {};
+ collections.map(c => {
+ let typeName = arvadosTypeName(c.owner_uuid);
+ if (!(typeName in owners))
+ owners[typeName] = [];
+ owners[typeName].push(c.owner_uuid);
+ });
+
+ let lookup = {};
+ let prom_1 = new Promise(accept => accept());
+ for (let typeName in owners) {
+ let filters_1 = [
+ ['uuid', 'in', owners[typeName]]
+ ];
+ prom_1 = prom_1.then(() => makeArvadosRequest(arvHost, arvToken,
+ '/arvados/v1/' + typeName + 's?filters=' +
+ encodeURIComponent(JSON.stringify(filters_1))));
+ prom_1 = prom_1.then(xhr => xhr.response.items.map(item => (
+ lookup[item.uuid] = item)));
+ }
+
+ prom_1 = prom_1.then(() => lookup);
+
+ return prom_1;
+ });
+
+ //let ownerLookup = {};
+ //prom = prom.then(lookup => (ownerLookup = lookup));
+
+ prom = prom.then(ownerLookup =>
+ this.setState({
+ 'numPages': numPages,
+ 'rows': this.prepareRows(collections, ownerLookup)
+ }));
+ }
+
+ componentWillReceiveProps(nextProps, nextState) {
+ this.props = nextProps;
+ this.fetchItems();
+ }
+
+ render({ app, ownerUuid, activePage, getPageUrl }, { rows, numPages }) {
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+WBCollectionListing.defaultProps = {
+ 'itemsPerPage': 100,
+ 'ownerUuid': null
+};
+
+export default WBCollectionListing;
diff --git a/frontend/src/js/misc/wb-arvados-collection.js b/frontend/src/js/misc/wb-arvados-collection.js
new file mode 100644
index 0000000..751a19a
--- /dev/null
+++ b/frontend/src/js/misc/wb-arvados-collection.js
@@ -0,0 +1,50 @@
+import makeArvadosRequest from 'make-arvados-request';
+
+class WBArvadosCollection {
+ constructor(arvHost, arvToken, uuid) {
+ this.arvHost = arvHost;
+ this.arvToken = arvToken;
+ this.uuid = uuid;
+ this.meta = null;
+ }
+
+ fetchMeta() {
+ let prom = makeArvadosRequest(this.arvHost, this.arvToken,
+ '/arvados/v1/collections/' + this.uuid);
+ prom = prom.then(xhr => {
+ this.meta = xhr.response;
+ });
+ return prom;
+ }
+
+ parseManifest() {
+ if (this.meta === null)
+ throw Error('You must call fetchMeta() first and wait for the returned Promise.');
+
+ let manifest = this.meta.manifest_text;
+ let streams = manifest.split('\n');
+ this.content = streams.map(s => {
+ let tokens = s.split(' ');
+ let streamName = tokens[0];
+ let rx = /^[a-f0-9]{32}\+[0-9]+/;
+ let n = tokens.map(t => rx.exec(t));
+ n = n.indexOf(null);
+ let locators = tokens.slice(1, n)
+ let fileTokens = tokens.slice(n);
+ let fileNames = fileTokens.map(t => t.split(':')[2]);
+ let fileSizes = {};
+ fileTokens.map(t => {
+ let [ start, end, name ] = t.split(':');
+ if (!(name in fileSizes))
+ fileSizes[name] = 0;
+ fileSizes[name] += Number(end) - Number(start);
+ });
+ fileSizes = fileNames.map(n => fileSizes[n]);
+ return [ streamName, fileNames, fileSizes ];
+ });
+
+ return this.content;
+ }
+}
+
+export WBArvadosCollection;
diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js
index e4a71e7..47d3311 100644
--- a/frontend/src/js/page/wb-app.js
+++ b/frontend/src/js/page/wb-app.js
@@ -58,7 +58,8 @@ class WBApp extends Component {
+ appState={ this.appState }
+ app={ this } />
diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js
index 6e78c35..9efb555 100644
--- a/frontend/src/js/page/wb-browse.js
+++ b/frontend/src/js/page/wb-browse.js
@@ -6,19 +6,26 @@ import WBInlineSearch from 'wb-inline-search';
import WBProjectCrumbs from 'wb-project-crumbs';
import WBTabs from 'wb-tabs';
import WBProcessListing from 'wb-process-listing';
+import WBCollectionListing from 'wb-collection-listing';
class WBBrowse extends Component {
- route(params) {
- route('/browse/' +
+ getUrl(params) {
+ let res = '/browse/' +
('ownerUuid' in params ? params.ownerUuid : (this.props.ownerUuid || '')) + '/' +
('activePage' in params ? params.activePage : (this.props.activePage || '')) + '/' +
('objTypeTab' in params ? params.objTypeTab : (this.props.objTypeTab || '')) + '/' +
('collectionPage' in params ? params.collectionPage : (this.props.collectionPage || '')) + '/' +
('processPage' in params ? params.processPage : (this.props.processPage || '')) + '/' +
- ('workflowPage' in params ? params.workflowPage : (this.props.workflowPage || '')));
+ ('workflowPage' in params ? params.workflowPage : (this.props.workflowPage || ''));
+
+ return res;
+ }
+
+ route(params) {
+ route(this.getUrl(params));
}
- render({ ownerUuid, activePage, appCallbacks, appState,
+ render({ ownerUuid, activePage, appCallbacks, appState, app,
objTypeTab, collectionPage, processPage, workflowPage }) {
return (
@@ -51,13 +58,19 @@ class WBBrowse extends Component {
{
(!objTypeTab || objTypeTab === 'collection') ? (
- null
+ this.getUrl({ 'collectionPage': i }) } />
+
) : (objTypeTab === 'process' ? (
this.route({ 'processPage': i }) } />
+
) : (objTypeTab === 'workflow' ? (
null
) : null))
diff --git a/frontend/src/js/widget/wb-pagination.js b/frontend/src/js/widget/wb-pagination.js
index 8e45602..5d0ff89 100644
--- a/frontend/src/js/widget/wb-pagination.js
+++ b/frontend/src/js/widget/wb-pagination.js
@@ -1,7 +1,7 @@
import { h, Component } from 'preact';
class WBPagination extends Component {
- renderVisiblePages(numPages, activePage, chunkSize, onPageChanged) {
+ renderVisiblePages(numPages, activePage, chunkSize, onPageChanged, getPageUrl) {
let visible = {};
let begActChnk = activePage - Math.floor(chunkSize / 2);
@@ -23,8 +23,8 @@ class WBPagination extends Component {
res.push((
- { e.preventDefault(); onPageChanged(activePage - 1); } }>Previous
+ this.changePage(e, activePage - 1) }>Previous
));
@@ -33,35 +33,42 @@ class WBPagination extends Component {
if (i > prev + 1)
res.push((
- { e.preventDefault(); onPageChanged(i - 1); } }>...
+ this.changePage(e, i - 1) }>...
));
prev = i;
res.push((
- { e.preventDefault(); onPageChanged(i); } }>{ i + 1 }
+ this.changePage(e, i) }>{ i + 1 }
));
}
res.push((
= numPages - 1 ? "page-item disabled" : "page-item" }>
- { e.preventDefault(); onPageChanged(activePage + 1); } }>Next
+ this.changePage(e, activePage + 1) }>Next
));
return res;
}
- render({ numPages, activePage, chunkSize, onPageChanged }) {
+ changePage(e, pageIdx) {
+ if (this.props.onPageChanged) {
+ e.preventDefault();
+ this.props.onPageChanged(pageIdx);
+ }
+ }
+
+ render({ numPages, activePage, chunkSize, onPageChanged, getPageUrl }) {
return (
);
@@ -69,7 +76,8 @@ class WBPagination extends Component {
}
WBPagination.defaultProps = {
- 'chunkSize': 5
+ 'chunkSize': 5,
+ 'getPageUrl': () => ('#')
};
export default WBPagination;