@@ -1,11 +1,115 @@ | |||||
import { h, Component } from 'preact'; | import { h, Component } from 'preact'; | ||||
import WBManifestWorkerWrapper from 'wb-manifest-worker-wrapper'; | |||||
import makeArvadosRequest from 'make-arvados-request'; | |||||
import WBTable from 'wb-table'; | |||||
import WBPagination from 'wb-pagination'; | |||||
function unescapeName(name) { | |||||
return name.replace(/(\\\\|\\[0-9]{3})/g, | |||||
(_, $1) => ($1 === '\\\\' ? '\\' : String.fromCharCode(parseInt($1.substr(1), 8)))); | |||||
} | |||||
class WBBrowseDialogCollectionContent extends Component { | class WBBrowseDialogCollectionContent extends Component { | ||||
render() { | |||||
constructor(...args) { | |||||
super(...args); | |||||
this.state.manifestWorker = new WBManifestWorkerWrapper(); | |||||
this.state.mode = 'manifestDownload'; | |||||
this.state.rows = []; | |||||
} | |||||
componentDidMount() { | |||||
const { app, collectionUuid } = this.props; | |||||
const { arvHost, arvToken } = app.state; | |||||
const { manifestWorker } = this.state; | |||||
let prom = makeArvadosRequest(arvHost, arvToken, | |||||
'/arvados/v1/collections/' + collectionUuid); | |||||
let streams; | |||||
prom = prom.then(xhr => { | |||||
streams = xhr.response.manifest_text.split('\n'); | |||||
const paths = streams.filter(s => s).map(s => { | |||||
const n = s.indexOf(' '); | |||||
return unescapeName(s.substr(0, n)); | |||||
}); | |||||
return manifestWorker.postMessage([ 'precreatePaths', paths ]); | |||||
}); | |||||
prom = prom.then(() => { | |||||
this.setState({ 'mode': 'manifestParse' }); | |||||
let prom_1 = new Promise(accept => accept()); | |||||
for (let i = 0; i < streams.length; i++) { | |||||
prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'parseStream', streams[i] ])); | |||||
prom_1 = prom_1.then(() => manifestWorker.postMessage([ 'listDirectory', '.' + this.props.collectionPath, true ])); | |||||
prom_1 = prom_1.then(e => this.prepareRows(e.data[1])); | |||||
} | |||||
return prom_1; | |||||
}); | |||||
prom = prom.then(() => manifestWorker.postMessage([ 'listDirectory', '.' + this.props.collectionPath, true ])); | |||||
prom = prom.then(e => { | |||||
this.state.mode = 'browsingReady'; | |||||
this.prepareRows(e.data[1]) | |||||
}); | |||||
} | |||||
componentWillReceiveProps(nextProps) { | |||||
this.props = nextProps; | |||||
if (this.state.mode !== 'browsingReady') | |||||
return; | |||||
let prom = this.state.manifestWorker.postMessage([ | |||||
'listDirectory', '.' + this.props.collectionPath, true | |||||
]); | |||||
prom = prom.then(e => this.prepareRows(e.data[1])); | |||||
} | |||||
prepareRows(listing) { | |||||
const { makeSelectionCell, collectionPath, navigate, | |||||
page, itemsPerPage, collectionUuid, textSearch } = this.props; | |||||
const textLower = textSearch.toLowerCase(); | |||||
listing = listing.filter(it => (it[1].toLowerCase().indexOf(textLower) !== -1)); | |||||
const numPages = Math.ceil(listing.length / itemsPerPage); | |||||
const rows = listing.slice(page * itemsPerPage, | |||||
(page + 1) * itemsPerPage).map(it => [ | |||||
makeSelectionCell(collectionUuid + '/' + collectionPath + '/' + it[1]), | |||||
it[0] === 'd' ? ( | |||||
<a href="#" onclick={ e => { | |||||
e.preventDefault(); | |||||
navigate({ 'collectionPath': collectionPath + '/' + it[1], | |||||
'bottomPage': 0 }); | |||||
} }>{ it[1] }</a> | |||||
) : it[1], | |||||
it[0] === 'f' ? filesize(it[2]) : '' | |||||
]); | |||||
this.setState({ rows, numPages }); | |||||
} | |||||
render({ page, navigate }, { rows, mode, numPages }) { | |||||
return ( | return ( | ||||
<div>WBBrowseDialogCollectionContent</div> | |||||
<div> | |||||
{ mode === 'browsingReady' ? ( | |||||
null | |||||
) : [ | |||||
<div>{ mode === 'manifestParse' ? 'Parsing manifest...' : 'Downloading manifest...' }</div>, | |||||
<div class="progress my-2"> | |||||
<div class={ 'progress-bar progress-bar-striped progress-bar-animated' + | |||||
(mode === 'manifestParse' ? ' bg-success': '') } role="progressbar" | |||||
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div> | |||||
</div> | |||||
] } | |||||
<WBTable headerClasses={ ['col-sm-1', 'col-sm-4', 'col-sm-4'] } | |||||
columns={ [ '', 'Name', 'Size' ] } rows={ rows } /> | |||||
<WBPagination numPages={ numPages } activePage={ page } | |||||
onPageChanged={ i => navigate({ 'bottomPage': i }) } /> | |||||
</div> | |||||
); | ); | ||||
} | } | ||||
} | } | ||||
WBBrowseDialogCollectionContent.defaultProps = { | |||||
'itemsPerPage': 20 | |||||
}; | |||||
export default WBBrowseDialogCollectionContent; | export default WBBrowseDialogCollectionContent; |
@@ -25,7 +25,7 @@ class WBBrowseDialogCollectionList extends Component { | |||||
] : []).concat([ | ] : []).concat([ | ||||
( | ( | ||||
<a href="#" onclick={ e => { e.preventDefault(); | <a href="#" onclick={ e => { e.preventDefault(); | ||||
navigate('/browse-dialog/content/' + it.uuid); } }>{ it.name }</a> | |||||
navigate('/browse-dialog/content/' + it.uuid + '////'); } }>{ it.name }</a> | |||||
), | ), | ||||
it.uuid | it.uuid | ||||
])); | ])); | ||||
@@ -13,7 +13,7 @@ import { createHashHistory } from 'history'; | |||||
// /browse-dialog/browse/( owner-uuid )/( project-page )/( text-search ) | // /browse-dialog/browse/( owner-uuid )/( project-page )/( text-search ) | ||||
// /browse-dialog/users//( users-page )/( text-search ) | // /browse-dialog/users//( users-page )/( text-search ) | ||||
// /browse-dialog/shared-with-me//( project-page )/( collection-page )/( text-search ) | // /browse-dialog/shared-with-me//( project-page )/( collection-page )/( text-search ) | ||||
// /browse-dialog/content/( collection-uuid )//( content-page )/( text-search ) | |||||
// /browse-dialog/content/( collection-uuid )//( content-page )/( text-search )/( collection-path ) | |||||
// | // | ||||
// general pattern therefore: | // general pattern therefore: | ||||
// /browse-dialog/( mode )/( uuid )/( top-page )/( bottom-page )/( text-search ) | // /browse-dialog/( mode )/( uuid )/( top-page )/( bottom-page )/( text-search ) | ||||
@@ -40,6 +40,8 @@ class WBBrowseDialog extends Component { | |||||
this.state.mode = 'browse'; | this.state.mode = 'browse'; | ||||
this.state.topPage = 0; | this.state.topPage = 0; | ||||
this.state.bottomPage = 0; | this.state.bottomPage = 0; | ||||
this.state.collectionPath = ''; | |||||
this.state.textSearch = ''; | |||||
} | } | ||||
navigateBack() { | navigateBack() { | ||||
@@ -56,7 +58,8 @@ class WBBrowseDialog extends Component { | |||||
'uuid' in url ? url.uuid : this.state.uuid, | 'uuid' in url ? url.uuid : this.state.uuid, | ||||
'topPage' in url ? url.topPage : this.state.topPage, | 'topPage' in url ? url.topPage : this.state.topPage, | ||||
'bottomPage' in url ? url.bottomPage : this.state.bottomPage, | 'bottomPage' in url ? url.bottomPage : this.state.bottomPage, | ||||
'textSearch' in url ? url.textSearch : this.state.textSearch | |||||
'textSearch' in url ? url.textSearch : this.state.textSearch, | |||||
encodeURIComponent('collectionPath' in url ? url.collectionPath : this.state.collectionPath) | |||||
].join('/'); | ].join('/'); | ||||
} | } | ||||
@@ -64,12 +67,13 @@ class WBBrowseDialog extends Component { | |||||
if (useHistory) | if (useHistory) | ||||
this.state.history.push(this.state.currentUrl); | this.state.history.push(this.state.currentUrl); | ||||
let [ _1, _2, mode, uuid, topPage, bottomPage, textSearch ] = url.split('/'); | |||||
let [ _1, _2, mode, uuid, topPage, bottomPage, textSearch, collectionPath ] = url.split('/'); | |||||
topPage = parseInt(topPage, 10) || 0; | topPage = parseInt(topPage, 10) || 0; | ||||
bottomPage = parseInt(bottomPage, 10) || 0; | bottomPage = parseInt(bottomPage, 10) || 0; | ||||
collectionPath = decodeURIComponent(collectionPath); | |||||
this.setState({ | this.setState({ | ||||
'currentUrl': url, | 'currentUrl': url, | ||||
mode, uuid, topPage, bottomPage, textSearch | |||||
mode, uuid, topPage, bottomPage, textSearch, collectionPath | |||||
}); | }); | ||||
} | } | ||||
@@ -130,7 +134,11 @@ class WBBrowseDialog extends Component { | |||||
); | ); | ||||
} | } | ||||
render({ app, id, selectMany, selectWhat }, { history, currentUrl, mode, uuid, topPage, bottomPage, textSearch }) { | |||||
render({ app, id, selectMany, selectWhat }, | |||||
{ history, currentUrl, mode, uuid, | |||||
topPage, bottomPage, textSearch, | |||||
collectionPath }) { | |||||
return ( | return ( | ||||
<div class="modal" id={ id } tabindex="-1" role="dialog"> | <div class="modal" id={ id } tabindex="-1" role="dialog"> | ||||
<div class="modal-dialog modal-lg" role="document"> | <div class="modal-dialog modal-lg" role="document"> | ||||
@@ -197,8 +205,12 @@ class WBBrowseDialog extends Component { | |||||
{ (mode === 'content') ? ( | { (mode === 'content') ? ( | ||||
<div> | <div> | ||||
<h5>Content</h5> | <h5>Content</h5> | ||||
<WBBrowseDialogCollectionContent app={ app } parent={ this } | |||||
selectMany={ selectMany } selectWhat={ selectWhat }/> | |||||
<WBBrowseDialogCollectionContent app={ app } | |||||
collectionUuid={ uuid } collectionPath={ collectionPath } | |||||
page={ bottomPage } selectWhat={ selectWhat } | |||||
makeSelectionCell={ uuid => this.makeSelectionCell(uuid) } | |||||
navigate={ url => this.navigate(url) } | |||||
textSearch={ textSearch } /> | |||||
</div> | </div> | ||||
) : (selectWhat !== 'owner' && mode === 'browse') ? ( | ) : (selectWhat !== 'owner' && mode === 'browse') ? ( | ||||