Commit 0c17b53f authored by hh1966's avatar hh1966
Browse files

Add image field to research plan detail

parent 52c414ea
Pipeline #50769 failed with stage
......@@ -65,6 +65,32 @@ module Chemotion
{svg_path: svg_file_name}
end
desc "Save image file to filesystem"
params do
requires :file, type: File
optional :replace, type: String
end
post :image do
file_name = params[:file][:filename]
file_extname = File.extname(file_name)
public_name = "#{SecureRandom.uuid}#{file_extname}"
public_path = "public/images/research_plans/#{public_name}"
File.open(public_path, 'wb') do |file|
file.write(params[:file][:tempfile].read)
end
if params[:replace]
File.delete("public/images/research_plans/#{params[:replace]}")
end
{
file_name: file_name,
public_name: public_name
}
end
desc "Create a research plan"
params do
requires :name, type: String, desc: "Research plan name"
......
......@@ -193,22 +193,22 @@ export default class ResearchPlanDetails extends Component {
<ListGroup fill="true">
<ListGroupItem>
<ResearchPlanDetailsNameField value={name}
disabled={research_plan.isMethodDisabled('name')}
onChange={this.handleNameChange.bind(this)} />
disabled={research_plan.isMethodDisabled('name')}
onChange={this.handleNameChange.bind(this)} />
<ResearchPlanDetailsBody body={body}
disabled={research_plan.isMethodDisabled('body')}
onChange={this.handleBodyChange.bind(this)}
onDrop={this.handleBodyDrop.bind(this)}
onAdd={this.handleBodyAdd.bind(this)}
onDelete={this.handleBodyDelete.bind(this)} />
disabled={research_plan.isMethodDisabled('body')}
onChange={this.handleBodyChange.bind(this)}
onDrop={this.handleBodyDrop.bind(this)}
onAdd={this.handleBodyAdd.bind(this)}
onDelete={this.handleBodyDelete.bind(this)} />
<ResearchPlanDetailsAttachments attachments={attachments}
onDrop={this.handleAttachmentDrop.bind(this)}
onDelete={this.handleAttachmentDelete.bind(this)}
onUndoDelete={this.handleAttachmentUndoDelete.bind(this)}
onDownload={this.handleAttachmentDownload.bind(this)}
onEdit={this.handleAttachmentEdit.bind(this)} />
onDrop={this.handleAttachmentDrop.bind(this)}
onDelete={this.handleAttachmentDelete.bind(this)}
onUndoDelete={this.handleAttachmentUndoDelete.bind(this)}
onDownload={this.handleAttachmentDownload.bind(this)}
onEdit={this.handleAttachmentEdit.bind(this)} />
</ListGroupItem>
</ListGroup>
);
......@@ -307,7 +307,7 @@ export default class ResearchPlanDetails extends Component {
return (
<Panel bsStyle={research_plan.isPendingToSave ? 'info' : 'primary'}
className="panel-detail">
className="panel-detail research-plan-details">
{this.renderPanelHeading(research_plan)}
{this.renderPanelBody(research_plan, edit)}
</Panel>
......
......@@ -77,6 +77,26 @@ export default class ResearchPlansFetcher {
return promise();
}
static updateImageFile(image_file, replace) {
var data = new FormData();
data.append('file', image_file);
if (replace) {
data.append('replace', replace);
}
let promise = ()=> fetch('/api/v1/research_plans/image', {
credentials: 'same-origin',
method: 'post',
body: data
}).then((response) => {
return response.json()
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise();
}
static create(researchPlan) {
const files = (researchPlan.attachments || []).filter(a => a.is_new && !a.is_deleted);
const promise = fetch('/api/v1/research_plans/', {
......
......@@ -47,7 +47,14 @@ export default class ResearchPlan extends Element {
case 'table':
break;
case 'image':
break;
this.body.push({
id: uuidv4(),
type: 'image',
value: {
file_name: null,
file_path: null,
}
})
}
}
......
......@@ -21,13 +21,15 @@ export default class ResearchPlanDetailsBody extends Component {
return (
<div className="research-plan-details-body">
<ControlLabel>Body</ControlLabel>
{fields}
<Row>
<Col md={12}>
<div className="research-plan-details-body-footer">
<ResearchPlanDetailsAddField onAdd={onAdd}/>
<ResearchPlanDetailsDropTarget index={fields.length}/>
<ResearchPlanDetailsDropTarget index={fields.length}/>
<div>
<ControlLabel>Add field</ControlLabel>
<div>
<ResearchPlanDetailsAddField onAdd={onAdd}/>
</div>
</div>
</Col>
</Row>
......
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { FormGroup, Button, Row, Col } from 'react-bootstrap'
import { FormGroup, Button, Row, Col, ControlLabel } from 'react-bootstrap'
import ResearchPlanDetailsDragSource from './ResearchPlanDetailsDragSource'
import ResearchPlanDetailsDropTarget from './ResearchPlanDetailsDropTarget'
import RichTextField from './ResearchPlanDetailsRichTextField'
import KetcherField from './ResearchPlanDetailsKetcherField'
import ResearchPlanDetailsRichTextField from './ResearchPlanDetailsRichTextField'
import ResearchPlanDetailsKetcherField from './ResearchPlanDetailsKetcherField'
import ResearchPlanDetailsImageField from './ResearchPlanDetailsImageField'
export default class ResearchPlanDetailsField extends Component {
render() {
let { field, index, disabled, onChange, onDrop, onDelete } = this.props
let field_component
let label, component
switch (field.type) {
case 'richtext':
field_component = <RichTextField key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
label = 'Text'
component = <ResearchPlanDetailsRichTextField key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
case 'ketcher':
field_component = <KetcherField key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
label = 'Schema'
component = <ResearchPlanDetailsKetcherField key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
break;
case 'image':
label = 'Image'
component = <ResearchPlanDetailsImageField key={field.id}
field={field} index={index} disabled={disabled}
onChange={onChange.bind(this)} />
}
return (
<Row>
<Col md={12}>
<div className="research-plan-details-body-seperator">
<ResearchPlanDetailsDropTarget index={index}/>
</Col>
<Col md={12}>
<div className="field">
<Button bsStyle="danger" bsSize="xsmall" onClick={() => onDelete(field.id)} >
<i className="fa fa-times"></i>
</Button>
<ResearchPlanDetailsDragSource index={index} onDrop={onDrop.bind(this)}/>
<ResearchPlanDetailsDropTarget index={index}/>
</div>
</Col>
<Col md={12}>
<div className="research-plan-details-body-field">
<ControlLabel>{label}</ControlLabel>
<FormGroup>
{field_component}
{component}
</FormGroup>
</div>
</Col>
......
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Dropzone from 'react-dropzone'
import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher'
export default class ResearchPlanDetailsImageField extends Component {
handleDrop(files) {
let { field, onChange } = this.props
let image_file = files[0]
let replace = field.value.public_name
// upload new image
ResearchPlansFetcher.updateImageFile(image_file, replace).then((value) => {
// update research plan
onChange(value, field.id)
});
}
render() {
let { field } = this.props
let content
if (field.value.public_name) {
let src = '/images/research_plans/' + field.value.public_name
content = (
<div className="image-container">
<img src={src} alt={field.value.file_name} />
</div>
)
} else {
content = <p>Drop Files, or Click to Select.</p>
}
return (
<Dropzone
accept="image/*"
multiple={false}
onDrop={files => this.handleDrop(files)}
className="dropzone"
>
{content}
</Dropzone>
);
}
}
ResearchPlanDetailsImageField.propTypes = {
field: PropTypes.object,
index: PropTypes.number,
disabled: PropTypes.bool,
onChange: PropTypes.func,
}
......@@ -65,10 +65,12 @@ export default class ResearchPlanDetailsKetcherField extends Component {
}
renderStructureEditorModal(field) {
const { showStructureEditor } = this.state
const molfile = field.value.sdf_file;
return(
<StructureEditorModal key={field.id}
showModal={this.state.showStructureEditor}
showModal={showStructureEditor}
onSave={this.handleStructureEditorSave.bind(this)}
onCancel={this.handleStructureEditorCancel.bind(this)}
molfile={molfile} />
......
......@@ -15,26 +15,34 @@ export default class ResearchPlanDetailsStatic extends Component {
let { name, body } = this.props
let fields = body.map((field) => {
let field_html
let html
switch (field.type) {
case 'richtext':
field_html = <QuillViewer value={field.value} />
html = <QuillViewer value={field.value} />
break;
case 'ketcher':
let svgPath = '/images/research_plans/' + field.value.svg_file
field_html = (
html = (
<div className="svg-container-static">
<SVG src={svgPath} className="molecule-mid" />
</div>
)
break;
case 'image':
let src = '/images/research_plans/' + field.value.public_name
html = (
<div className="image-container">
<img src={src} alt={field.value.file_name} />
</div>
)
}
return (
<Row key={field.id}>
<Col>
{field_html}
<Col md={12}>
{html}
</Col>
</Row>
)
......@@ -44,7 +52,9 @@ export default class ResearchPlanDetailsStatic extends Component {
<div>
<h4>{name}</h4>
{fields}
<div className="research-plan-details-static">
{fields}
</div>
</div>
)
}
......
.research-plan-details-body {
.research-plan-details {
margin-bottom: 20px;
.research-plan-details-body-seperator,
.research-plan-details-body-footer {
height: 40px;
position: relative;
}
.field {
label {
margin-top: 5px;
}
.research-plan-details-body-seperator {
button {
float: right;
margin-top: 10px;
}
.drag-source {
float: right;
margin-top: 14px;
margin-top: 5px;
margin-right: 5px;
}
.drop-target {
left: 0;
right: 50px;
}
}
.research-plan-details-body-footer {
.drop-target {
left: 280px;
right: 0;
}
}
.form-group {
......@@ -38,12 +21,10 @@
}
.drop-target {
position: absolute;
height: 40px;
padding-top: 10px;
height: 20px;
.indicator {
height: 13px;
height: 10px;
}
&.can-drop {
......@@ -61,7 +42,7 @@
.add-field {
float: left;
margin-top: 10px;
margin-bottom: 20px;
button {
height: 30px;
......@@ -69,6 +50,35 @@
margin-right: 10px;
}
}
.image-container {
text-align: center;
margin-bottom: 20px;
img {
max-width: 100%;
}
}
.dropzone {
min-height: 50px;
width: 100%;
border: 3px dashed lightgray;
.image-container {
margin: 15px;
}
p {
text-align: center;
padding-top: 12px;
color: grey;
}
}
.ql-hidden {
display: none;
}
}
.svg-container-static {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment