@@ -21,3 +21,7 @@ div.wb-json-viewer { | |||
font-family: "Courier New", fixed-width; | |||
white-space: pre-wrap; | |||
} | |||
textarea.wb-json-editor { | |||
font-family: "Courier New", fixed-width; | |||
} |
@@ -0,0 +1,16 @@ | |||
import makeArvadosRequest from 'make-arvados-request'; | |||
import arvadosTypeName from 'arvados-type-name'; | |||
function wbUpdateField(arvHost, arvToken, uuid, fieldName, fieldValue) { | |||
const typeName = arvadosTypeName(uuid); | |||
const data = {}; | |||
data[fieldName] = fieldValue; | |||
const prom = makeArvadosRequest(arvHost, arvToken, | |||
'/arvados/v1/' + typeName + 's/' + uuid, { | |||
method: 'PUT', | |||
data: JSON.stringify(data) | |||
}); | |||
return prom; | |||
} | |||
export default wbUpdateField; |
@@ -9,18 +9,54 @@ import WBNameAndUuid from 'wb-name-and-uuid'; | |||
import WBAccordion from 'wb-accordion'; | |||
import wbFormatSpecialValue from 'wb-format-special-value'; | |||
import WBJsonViewer from 'wb-json-viewer'; | |||
import WBJsonEditor from 'wb-json-editor'; | |||
import wbUpdateField from 'wb-update-field'; | |||
class WBCollectionFields extends Component { | |||
componentDidMount() { | |||
this.prepareRows(); | |||
this.fetchData(); | |||
} | |||
componentWillReceiveProps(nextProps) { | |||
this.props = nextProps; | |||
this.prepareRows(); | |||
this.fetchData(); | |||
} | |||
prepareRows() { | |||
prepareRows(item) { | |||
const { app } = this.props; | |||
const { arvHost, arvToken } = app.state; | |||
let rows = [ | |||
[ 'Name', item.name ], | |||
[ 'Description', wbFormatSpecialValue(item.description) ], | |||
[ 'Properties', ( | |||
<WBJsonEditor name="Properties" app={ app } value={ item.properties } | |||
onChange={ value => wbUpdateField(arvHost, arvToken, item.uuid, 'properties', value) | |||
.then(() => { item.properties = value; this.prepareRows(item); }) } /> | |||
) ], | |||
[ 'Portable Data Hash', item.portable_data_hash ], | |||
[ 'Replication Desired', item.replication_desired ? item.replication_desired : ( | |||
<i>{ String(item.replication_desired) }</i> | |||
) ], | |||
[ 'Replication Confirmed', item.replication_confirmed ? item.replication_confirmed : ( | |||
<i>{ String(item.replication_confirmed) }</i> | |||
) ], | |||
[ 'Replication Confirmed At', wbFormatDate(item.replication_confirmed_at) ], | |||
[ 'Trash At', wbFormatDate(item.trash_at) ], | |||
[ 'Delete At', wbFormatDate(item.delete_at) ], | |||
[ 'Is Trashed', String(item.is_trashed) ], | |||
[ 'Current Version UUID', ( | |||
<WBNameAndUuid app={ app } uuid={ item.current_version_uuid } /> | |||
) ], | |||
[ 'Version', item.version ], | |||
[ 'Preserve Version', String(item.preserve_version) ], | |||
[ 'File Count', item.file_count ], | |||
[ 'Total Size', filesize(item.file_size_total) ] | |||
]; | |||
this.setState({ 'rows': rows }); | |||
} | |||
fetchData() { | |||
let { uuid, app } = this.props; | |||
let { arvHost, arvToken } = app.state; | |||
@@ -35,34 +71,7 @@ class WBCollectionFields extends Component { | |||
const item = xhr.response.items[0]; | |||
if (!item) | |||
throw Error('Item not found'); | |||
let rows = [ | |||
[ 'Name', item.name ], | |||
[ 'Description', wbFormatSpecialValue(item.description) ], | |||
[ 'Properties', ( | |||
<WBAccordion names={ ['Properties'] } cardHeaderClass="card-header-sm"> | |||
<WBJsonViewer app={ app } value={ item.properties } /> | |||
</WBAccordion> | |||
) ], | |||
[ 'Portable Data Hash', item.portable_data_hash ], | |||
[ 'Replication Desired', item.replication_desired ? item.replication_desired : ( | |||
<i>{ String(item.replication_desired) }</i> | |||
) ], | |||
[ 'Replication Confirmed', item.replication_confirmed ? item.replication_confirmed : ( | |||
<i>{ String(item.replication_confirmed) }</i> | |||
) ], | |||
[ 'Replication Confirmed At', wbFormatDate(item.replication_confirmed_at) ], | |||
[ 'Trash At', wbFormatDate(item.trash_at) ], | |||
[ 'Delete At', wbFormatDate(item.delete_at) ], | |||
[ 'Is Trashed', String(item.is_trashed) ], | |||
[ 'Current Version UUID', ( | |||
<WBNameAndUuid app={ app } uuid={ item.current_version_uuid } /> | |||
) ], | |||
[ 'Version', item.version ], | |||
[ 'Preserve Version', String(item.preserve_version) ], | |||
[ 'File Count', item.file_count ], | |||
[ 'Total Size', filesize(item.file_size_total) ] | |||
]; | |||
this.setState({ 'rows': rows }); | |||
this.prepareRows(item); | |||
}); | |||
} | |||
@@ -0,0 +1,60 @@ | |||
import { h, Component, createRef } from 'preact'; | |||
import WBJsonViewer from 'wb-json-viewer'; | |||
import WBAccordion from 'wb-accordion'; | |||
import WBDialog from 'wb-dialog'; | |||
class WBJsonEditor extends Component { | |||
constructor(...args) { | |||
super(...args); | |||
this.dialogRef = createRef(); | |||
} | |||
render({ app, name, value, stringify, pretty, onChange }, { editValue, parseError }) { | |||
return ( | |||
<div> | |||
<WBDialog title={ 'Edit ' + name } ref={ this.dialogRef } | |||
accept={ () => { | |||
onChange(JSON.parse(editValue)); | |||
} } | |||
canAccept={ () => { | |||
try { JSON.parse(editValue) } | |||
catch (exc) { this.setState({ parseError: exc.message }); return false; } | |||
return true; | |||
} }> | |||
<div> | |||
<textarea class="form-control wb-json-editor" value={ editValue } rows="10" | |||
onChange={ e => this.setState({ editValue: e.target.value }) } /> | |||
{ parseError ? ( | |||
<div class="alert alert-danger" role="alert"> | |||
{ parseError } | |||
</div> | |||
) : null } | |||
</div> | |||
</WBDialog> | |||
<WBAccordion names={ [ name ] } extraHeaderUi={ [ ( | |||
<button class="btn btn-link px-0" title="Edit" | |||
onclick={ () => { | |||
this.setState({ parseError: null, | |||
editValue: stringify ? | |||
pretty ? JSON.stringify(value, null, 2) | |||
: JSON.stringify(value) : value }); | |||
this.dialogRef.current.show(); | |||
} }> | |||
<i class="fas fa-edit text-secondary" /> | |||
</button> | |||
) ] } cardHeaderClass="card-header-sm"> | |||
<WBJsonViewer app={ app } value={ value } stringify={ stringify } | |||
pretty={ pretty } /> | |||
</WBAccordion> | |||
</div> | |||
); | |||
} | |||
} | |||
WBJsonEditor.defaultProps = { | |||
stringify: true, | |||
pretty: true, | |||
onChange: () => {} | |||
}; | |||
export default WBJsonEditor; |
@@ -39,7 +39,7 @@ class WBAccordion extends Component { | |||
this.setState({ ariaExpanded, collapseClass, buttonClass }); | |||
} | |||
render({ children, names, cardHeaderClass }, { domId, headerDomIds, | |||
render({ children, names, extraHeaderUi, cardHeaderClass }, { domId, headerDomIds, | |||
collapseDomIds, collapseClass, ariaExpanded, buttonClass }) { | |||
return ( | |||
@@ -53,6 +53,7 @@ class WBAccordion extends Component { | |||
onclick={ () => this.toggle(i) }> | |||
{ names[i] } | |||
</button> | |||
{ extraHeaderUi[i] } | |||
</h2> | |||
</div> | |||
@@ -70,7 +71,8 @@ class WBAccordion extends Component { | |||
}; | |||
WBAccordion.defaultProps = { | |||
'cardHeaderClass': 'card-header' | |||
cardHeaderClass: 'card-header', | |||
extraHeaderUi: [] | |||
}; | |||
export default WBAccordion; |
@@ -18,7 +18,7 @@ class WBDialog extends Component { | |||
$(this.modalRef.current).modal('hide'); | |||
} | |||
render({ title, children, accept, reject }) { | |||
render({ title, children, canAccept, accept, reject }) { | |||
return ( | |||
<form class="m-0"> | |||
<div class="modal" tabindex="-1" role="dialog" ref={ this.modalRef }> | |||
@@ -38,12 +38,12 @@ class WBDialog extends Component { | |||
<div class="modal-footer"> | |||
{ children[1] ? children[1] : [ | |||
<input type="submit" class="btn btn-primary" value="Accept" | |||
onclick={ e => { e.preventDefault(); this.hide(); accept(); } } />, | |||
onclick={ e => { e.preventDefault(); if (!canAccept()) return; this.hide(); accept(); } } />, | |||
<button type="button" class="btn btn-secondary" | |||
onclick={ () => { this.hide(); reject(); } }>Cancel</button> | |||
] } | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -53,9 +53,10 @@ class WBDialog extends Component { | |||
} | |||
WBDialog.defaultProps = { | |||
'title': 'Dialog', | |||
'accept': () => {}, | |||
'reject': () => {} | |||
title: 'Dialog', | |||
accept: () => {}, | |||
reject: () => {}, | |||
canAccept: () => true | |||
}; | |||
export default WBDialog; |