| @@ -21,12 +21,14 @@ class WBCollectionListing extends Component { | |||
| } | |||
| prepareRows(items, ownerLookup) { | |||
| let { app } = this.props; | |||
| let { app, renderRenameLink } = this.props; | |||
| return items.map(item => [ | |||
| (<div> | |||
| <div> | |||
| <a href={ urlForObject(item) }>{ item['name'] }</a> | |||
| <a href={ urlForObject(item) }> | |||
| { item['name'] } | |||
| </a> { renderRenameLink(item, () => this.fetchItems()) } | |||
| </div> | |||
| <div>{ item['uuid'] }</div> | |||
| </div>), | |||
| @@ -22,11 +22,14 @@ class WBProcessListing extends Component { | |||
| } | |||
| prepareRows(items) { | |||
| const { renderRenameLink } = this.props; | |||
| return items.map(item => [ | |||
| (<div> | |||
| <div> | |||
| <a href="#" | |||
| onclick={ e => { e.preventDefault(); route('/process/' + item['uuid']) }}>{ item['name'] }</a> | |||
| onclick={ e => { e.preventDefault(); route('/process/' + item['uuid']) }}> | |||
| { item['name'] } | |||
| </a> { renderRenameLink(item, () => this.fetchItems()) } | |||
| </div> | |||
| <div>{ item['uuid'] }</div> | |||
| </div>), | |||
| @@ -17,13 +17,15 @@ class WBProjectListing extends Component { | |||
| } | |||
| prepareRows(items) { | |||
| let { app } = this.props; | |||
| let { app, renderRenameLink } = this.props; | |||
| return items.map(item => [ | |||
| (<div> | |||
| <div> | |||
| <a href="#" | |||
| onclick={ e => { e.preventDefault(); route('/browse/' + item['uuid']) }}>{ item['name'] }</a> | |||
| onclick={ e => { e.preventDefault(); route('/browse/' + item['uuid']) }}> | |||
| { item['name'] } | |||
| </a> { renderRenameLink(item, () => this.fetchItems()) } | |||
| </div> | |||
| <div>{ item['uuid'] }</div> | |||
| </div>), | |||
| @@ -78,7 +80,8 @@ class WBProjectListing extends Component { | |||
| WBProjectListing.defaultProps = { | |||
| 'itemsPerPage': 100, | |||
| 'ownerUuid': null | |||
| 'ownerUuid': null, | |||
| 'renderRenameLink': () => null | |||
| }; | |||
| export default WBProjectListing; | |||
| @@ -6,6 +6,7 @@ import WBNameAndUuid from 'wb-name-and-uuid'; | |||
| import wbFetchObjects from 'wb-fetch-objects'; | |||
| import wbFormatDate from 'wb-format-date'; | |||
| import urlForObject from 'url-for-object'; | |||
| import arvadosObjectName from 'arvados-object-name'; | |||
| class WBWorkflowListing extends Component { | |||
| @@ -20,8 +21,18 @@ class WBWorkflowListing extends Component { | |||
| } | |||
| prepareRows(items, ownerLookup) { | |||
| const { renderRenameLink } = this.props; | |||
| return items.map(item => [ | |||
| ( <WBNameAndUuid uuid={ item.uuid } lookup={ { [item.uuid]: item } } /> ), | |||
| ( | |||
| <div> | |||
| <div> | |||
| <a href={ urlForObject(item) }> | |||
| { arvadosObjectName(item) } | |||
| </a> { renderRenameLink(item, () => this.fetchItems()) } | |||
| </div> | |||
| <div>{ item.uuid }</div> | |||
| </div> | |||
| ), | |||
| item.description, | |||
| ( <WBNameAndUuid uuid={ item.owner_uuid } lookup={ ownerLookup } /> ), | |||
| wbFormatDate(item.created_at), | |||
| @@ -0,0 +1,45 @@ | |||
| import { h, Component, createRef } from 'preact'; | |||
| import WBDialog from 'wb-dialog'; | |||
| import linkState from 'linkstate'; | |||
| import wbRenameObject from 'wb-rename-object'; | |||
| class WBRenameDialog extends Component { | |||
| constructor(...args) { | |||
| super(...args); | |||
| this.dialogRef = createRef(); | |||
| this.state.inputId = uuid.v4(); | |||
| } | |||
| show(item, callback) { | |||
| const { inputId } = this.state; | |||
| this.setState({ | |||
| 'item': item, | |||
| 'newName': null, | |||
| 'callback': callback || (() => {}) | |||
| }); | |||
| this.dialogRef.current.show(); | |||
| $('#' + inputId).focus(); | |||
| } | |||
| hide() { | |||
| this.dialogRef.current.hide(); | |||
| } | |||
| render({ app }, { item, newName, callback, inputId }) { | |||
| const { arvHost, arvToken } = app.state; | |||
| return ( | |||
| <WBDialog title="Rename" ref={ this.dialogRef } accept={ () => { | |||
| if (newName) | |||
| wbRenameObject(arvHost, arvToken, item.uuid, newName).then(callback); | |||
| } }> | |||
| <div> | |||
| <input type="text" class="form-control" id={ inputId } | |||
| placeholder={ item ? item.name : 'Type new name here' } | |||
| value={ newName } onChange={ linkState(this, 'newName') } /> | |||
| </div> | |||
| </WBDialog> | |||
| ); | |||
| } | |||
| } | |||
| export default WBRenameDialog; | |||
| @@ -0,0 +1,16 @@ | |||
| import makeArvadosRequest from 'make-arvados-request'; | |||
| import arvadosTypeName from 'arvados-type-name'; | |||
| function wbRenameObject(arvHost, arvToken, uuid, newName) { | |||
| const update = { | |||
| 'name': newName | |||
| }; | |||
| const typeName = arvadosTypeName(uuid); | |||
| return makeArvadosRequest(arvHost, arvToken, | |||
| '/arvados/v1/' + typeName + 's/' + | |||
| uuid + '?' + typeName + '=' + | |||
| encodeURIComponent(JSON.stringify(update)), | |||
| { 'method': 'PUT' }); | |||
| } | |||
| export default wbRenameObject; | |||
| @@ -1,4 +1,4 @@ | |||
| import { h, Component } from 'preact'; | |||
| import { h, Component, createRef } from 'preact'; | |||
| import { route } from 'preact-router'; | |||
| import WBNavbarCommon from 'wb-navbar-common'; | |||
| import WBProjectListing from 'wb-project-listing'; | |||
| @@ -8,8 +8,14 @@ import WBTabs from 'wb-tabs'; | |||
| import WBProcessListing from 'wb-process-listing'; | |||
| import WBCollectionListing from 'wb-collection-listing'; | |||
| import WBWorkflowListing from 'wb-workflow-listing'; | |||
| import WBRenameDialog from 'wb-rename-dialog'; | |||
| class WBBrowse extends Component { | |||
| constructor(...args) { | |||
| super(...args); | |||
| this.renameDialogRef = createRef(); | |||
| } | |||
| getUrl(params) { | |||
| let res = '/browse/' + | |||
| ('ownerUuid' in params ? params.ownerUuid : (this.props.ownerUuid || '')) + '/' + | |||
| @@ -26,11 +32,26 @@ class WBBrowse extends Component { | |||
| route(this.getUrl(params)); | |||
| } | |||
| renameDialog(item, callback) { | |||
| // throw Error('Not implemented'); | |||
| this.renameDialogRef.current.show(item, callback); | |||
| } | |||
| renderRenameLink(item, callback) { | |||
| return ( | |||
| <a href="#" title="Rename" onclick={ e => { e.preventDefault(); this.renameDialog(item, callback); } }> | |||
| <i class="fas fa-edit text-secondary"></i> | |||
| </a> | |||
| ); | |||
| } | |||
| render({ ownerUuid, activePage, appState, app, | |||
| objTypeTab, collectionPage, processPage, workflowPage }) { | |||
| return ( | |||
| <div> | |||
| <WBRenameDialog app={ app } ref={ this.renameDialogRef } /> | |||
| <WBNavbarCommon app={ app } activeItem={ !ownerUuid ? 'all-projects' : | |||
| (ownerUuid === app.state.currentUser.uuid ? 'home' : null) } /> | |||
| @@ -44,7 +65,8 @@ class WBBrowse extends Component { | |||
| ownerUuid={ ownerUuid } | |||
| itemsPerPage="5" | |||
| activePage={ Number(activePage || 0) } | |||
| onPageChanged={ i => route('/browse/' + (ownerUuid || '') + '/' + i)} /> | |||
| onPageChanged={ i => route('/browse/' + (ownerUuid || '') + '/' + i) } | |||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } /> | |||
| <WBTabs tabs={ [ | |||
| { 'id': 'collection', 'name': 'Collections', 'isActive': (!objTypeTab || objTypeTab === 'collection') }, | |||
| @@ -58,21 +80,24 @@ class WBBrowse extends Component { | |||
| ownerUuid={ ownerUuid } | |||
| itemsPerPage="20" | |||
| activePage={ Number(collectionPage || 0) } | |||
| getPageUrl={ i => this.getUrl({ 'collectionPage': i }) } /> | |||
| getPageUrl={ i => this.getUrl({ 'collectionPage': i }) } | |||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } /> | |||
| ) : (objTypeTab === 'process' ? ( | |||
| <WBProcessListing appState={ appState } | |||
| ownerUuid={ ownerUuid } | |||
| itemsPerPage="20" | |||
| activePage={ Number(processPage || 0) } | |||
| onPageChanged={ i => this.route({ 'processPage': i }) } /> | |||
| onPageChanged={ i => this.route({ 'processPage': i }) } | |||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } /> | |||
| ) : (objTypeTab === 'workflow' ? ( | |||
| <WBWorkflowListing app={ app } | |||
| ownerUuid={ ownerUuid } | |||
| itemsPerPage="20" | |||
| page={ Number(workflowPage || 0) } | |||
| getPageUrl={ i => this.getUrl({ 'workflowPage': i }) } /> | |||
| getPageUrl={ i => this.getUrl({ 'workflowPage': i }) } | |||
| renderRenameLink={ (it, cb) => this.renderRenameLink(it, cb) } /> | |||
| ) : null)) | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| import { h, Component } from 'preact'; | |||
| class WBDialog extends Component { | |||
| constructor(...args) { | |||
| super(...args); | |||
| this.state.id = uuid.v4(); | |||
| } | |||
| show() { | |||
| const { id } = this.state; | |||
| $('#' + id).modal(); | |||
| } | |||
| hide() { | |||
| const { id } = this.state; | |||
| $('#' + id).modal('hide'); | |||
| } | |||
| render({ title, children, accept, reject }, { id }) { | |||
| return ( | |||
| <form class="m-0"> | |||
| <div class="modal" id={ id } tabindex="-1" role="dialog"> | |||
| <div class="modal-dialog modal-lg" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title">{ title }</h5> | |||
| <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |||
| <span aria-hidden="true">×</span> | |||
| </button> | |||
| </div> | |||
| <div class="modal-body"> | |||
| { children[0] } | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <input type="submit" class="btn btn-primary" value="Accept" | |||
| onclick={ e => { e.preventDefault(); this.hide(); accept(); } } /> | |||
| <button type="button" class="btn btn-secondary" | |||
| onclick={ () => { this.hide(); reject(); } }>Cancel</button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| ); | |||
| } | |||
| } | |||
| WBDialog.defaultProps = { | |||
| 'title': 'Dialog', | |||
| 'accept': () => {}, | |||
| 'reject': () => {} | |||
| }; | |||
| export default WBDialog; | |||