| @@ -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') ? ( | ||||