Commit 270ab676 authored by hh1966's avatar hh1966
Browse files

Add export to research plan details using client side rendered html and an api using pandoc

parent 7c6bb38f
......@@ -97,9 +97,13 @@ gem 'axlsx', git: 'https://github.com/randym/axlsx'
gem 'rmagick'
gem 'rtf'
gem 'sablon', git: 'https://github.com/ComPlat/sablon'
# Import of elements from XLS and CSV file
gem 'roo', '>2.5.0'
# export reseearch plan
gem 'pandoc-ruby'
gem 'faraday', '~> 0.12.1'
gem 'faraday_middleware', '~> 0.12.1'
gem 'httparty'
......
......@@ -409,6 +409,7 @@ GEM
numerizer (0.1.1)
open4 (1.3.4)
orm_adapter (0.5.0)
pandoc-ruby (2.0.2)
paperclip (5.2.1)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
......@@ -696,6 +697,7 @@ DEPENDENCIES
net-ssh
nokogiri (~> 1.8.2)
openbabel (= 2.4.90.3)!
pandoc-ruby
paranoia (~> 2.0)
pg (~> 0.20.0)
pg_search
......
......@@ -42,6 +42,69 @@ module Chemotion
paginate(scope).map{|s| ElementPermissionProxy.new(current_user, s, user_ids).serialized}
end
desc "Create a research plan"
params do
requires :name, type: String, desc: "Research plan name"
requires :body, type: Array, desc: "Research plan body"
optional :collection_id, type: Integer, desc: "Collection ID"
end
post do
attributes = {
name: params[:name],
body: params[:body]
}
research_plan = ResearchPlan.new attributes
research_plan.creator = current_user
research_plan.save!
if col_id = params[:collection_id]
research_plan.collections << current_user.collections.find(col_id)
end
all_coll = Collection.get_all_collection_for_user(current_user.id)
research_plan.collections << all_coll
research_plan
end
desc "Return serialized research plan by id"
params do
requires :id, type: Integer, desc: "Research plan id"
end
route_param :id do
before do
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).read?
end
get do
research_plan = ResearchPlan.find(params[:id])
{research_plan: ElementPermissionProxy.new(current_user, research_plan, user_ids).serialized,
attachments: Entities::AttachmentEntity.represent(research_plan.attachments)}
end
end
desc "Update research plan by id"
params do
requires :id, type: Integer, desc: "Research plan id"
optional :name, type: String, desc: "Research plan name"
optional :body, type: Array, desc: "Research plan body"
end
route_param :id do
before do
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).update?
end
put do
attributes = declared(params, include_missing: false)
if research_plan = ResearchPlan.find(params[:id])
research_plan.update!(attributes)
end
{ research_plan: ElementPermissionProxy.new(current_user, research_plan, user_ids).serialized }
end
end
desc "Save svg file to filesystem"
params do
requires :svg_file, type: String, desc: "SVG raw file"
......@@ -91,6 +154,39 @@ module Chemotion
}
end
desc "Export research plan by id"
params do
requires :id, type: Integer, desc: "Research plan id"
requires :html, type: String, desc: "Client side rendered html"
optional :export_format, type: Symbol, desc: "Export format", values: [:html, :docx, :latex]
end
route_param :id do
before do
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).read?
end
post :export do
research_plan = ResearchPlan.find(params[:id])
file_name = "#{research_plan.name}.#{params[:export_format]}"
if params[:export_format]
# make src in html relative
params[:html].gsub! 'src="/images/', 'src="images/'
# convert using pandoc and stream as file
content_type "application/octet-stream"
header['Content-Disposition'] = "attachment; filename=\"#{file_name}\""
env['api.format'] = :binary
present PandocRuby.convert(params[:html], :from => :html, :to => params[:export_format], :resource_path => Rails.public_path)
else
# return plain html
env['api.format'] = :binary
present params[:html]
end
end
end
namespace :table_schemas do
desc "Return serialized table schemas of current user"
get do
......@@ -126,68 +222,6 @@ module Chemotion
end
end
desc "Create a research plan"
params do
requires :name, type: String, desc: "Research plan name"
requires :body, type: Array, desc: "Research plan body"
optional :collection_id, type: Integer, desc: "Collection ID"
end
post do
attributes = {
name: params[:name],
body: params[:body]
}
research_plan = ResearchPlan.new attributes
research_plan.creator = current_user
research_plan.save!
if col_id = params[:collection_id]
research_plan.collections << current_user.collections.find(col_id)
end
all_coll = Collection.get_all_collection_for_user(current_user.id)
research_plan.collections << all_coll
research_plan
end
desc "Return serialized research plan by id"
params do
requires :id, type: Integer, desc: "Research plan id"
end
route_param :id do
before do
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).read?
end
get do
research_plan = ResearchPlan.find(params[:id])
{research_plan: ElementPermissionProxy.new(current_user, research_plan, user_ids).serialized,
attachments: Entities::AttachmentEntity.represent(research_plan.attachments)}
end
end
desc "Update research plan by id"
params do
requires :id, type: Integer, desc: "Research plan id"
optional :name, type: String, desc: "Research plan name"
optional :body, type: Array, desc: "Research plan body"
end
route_param :id do
before do
error!('401 Unauthorized', 401) unless ElementPolicy.new(current_user, ResearchPlan.find(params[:id])).update?
end
put do
attributes = declared(params, include_missing: false)
if research_plan = ResearchPlan.find(params[:id])
research_plan.update!(attributes)
end
{ research_plan: ElementPermissionProxy.new(current_user, research_plan, user_ids).serialized }
end
end
end
end
end
......@@ -4,6 +4,7 @@ import ResearchPlan from '../models/ResearchPlan';
import AttachmentFetcher from './AttachmentFetcher';
import BaseFetcher from './BaseFetcher';
import { getFileName, downloadBlob } from '../utils/FetcherHelper'
export default class ResearchPlansFetcher {
static fetchById(id) {
......@@ -30,6 +31,30 @@ export default class ResearchPlansFetcher {
return BaseFetcher.fetchByCollectionId(id, queryParams, isSync, 'research_plans', ResearchPlan);
}
static create(researchPlan) {
const files = (researchPlan.attachments || []).filter(a => a.is_new && !a.is_deleted);
const promise = fetch('/api/v1/research_plans/', {
credentials: 'same-origin',
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(researchPlan.serialize())
}).then((response) => {
return response.json();
}).then((json) => {
if (files.length <= 0) {
return new ResearchPlan(json.research_plan);
}
return AttachmentFetcher.updateAttachables(files, 'ResearchPlan', json.research_plan.id, [])()
.then(() => new ResearchPlan(json.research_plan));
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static update(researchPlan) {
const newFiles = (researchPlan.attachments || []).filter(a => a.is_new && !a.is_deleted);
const delFiles = (researchPlan.attachments || []).filter(a => !a.is_new && a.is_deleted);
......@@ -97,24 +122,28 @@ export default class ResearchPlansFetcher {
return promise();
}
static create(researchPlan) {
const files = (researchPlan.attachments || []).filter(a => a.is_new && !a.is_deleted);
const promise = fetch('/api/v1/research_plans/', {
static export(researchPlan, html, exportFormat) {
let file_name
const promise = fetch('/api/v1/research_plans/' + researchPlan.id + '/export/', {
credentials: 'same-origin',
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(researchPlan.serialize())
body: JSON.stringify({
html: html,
export_format: exportFormat
})
}).then((response) => {
return response.json();
}).then((json) => {
if (files.length <= 0) {
return new ResearchPlan(json.research_plan);
if (response.ok) {
file_name = getFileName(response)
return response.blob()
} else {
console.log(response);
}
return AttachmentFetcher.updateAttachables(files, 'ResearchPlan', json.research_plan.id, [])()
.then(() => new ResearchPlan(json.research_plan));
}).then((blob) => {
downloadBlob(file_name, blob)
}).catch((errorMessage) => {
console.log(errorMessage);
});
......
import React, { Component } from 'react';
import ReactDOM from "react-dom"
import PropTypes from 'prop-types';
import { Panel, ListGroup, ListGroupItem, ButtonToolbar, Button, Tooltip, OverlayTrigger, Row, Col, Tabs, Tab } from 'react-bootstrap';
import SVG from 'react-inlinesvg';
......@@ -26,9 +27,10 @@ export default class ResearchPlanDetails extends Component {
const { research_plan } = props;
this.state = {
research_plan,
edit: true,
edit: false,
update: false
};
this.ref = React.createRef()
}
componentWillReceiveProps(nextProps) {
......@@ -177,6 +179,23 @@ export default class ResearchPlanDetails extends Component {
this.forceUpdate();
}
handleExport() {
const { research_plan } = this.state;
const bodyElements = ReactDOM.findDOMNode(this.ref.current).getElementsByClassName('field')
let html = ''
research_plan.body.map((field, index) => {
if (field.type == 'richtext') {
html += bodyElements[index].getElementsByClassName('ql-editor')[0].innerHTML
} else {
html += bodyElements[index].innerHTML
}
})
ResearchPlansFetcher.export(research_plan, html, 'docx')
}
// render functions
renderResearchPlanInfo(research_plan) {
......@@ -196,9 +215,20 @@ export default class ResearchPlanDetails extends Component {
const { update, edit } = this.state
const submitLabel = research_plan.isNew ? "Create" : "Save"
let exportButton
if (!edit) {
exportButton = (
<div className="pull-right">
<Button bsStyle="default" onClick={this.handleExport.bind(this)}>Export</Button>
</div>
)
}
return (
<ListGroup fill="true">
<ListGroupItem>
<ListGroupItem >
{exportButton}
<ResearchPlanDetailsName value={name}
disabled={research_plan.isMethodDisabled('name')}
onChange={this.handleNameChange.bind(this)}
......@@ -211,7 +241,8 @@ export default class ResearchPlanDetails extends Component {
onAdd={this.handleBodyAdd.bind(this)}
onDelete={this.handleBodyDelete.bind(this)}
update={update}
edit={edit} />
edit={edit}
ref={this.ref} />
<ResearchPlanDetailsAttachments attachments={attachments}
onDrop={this.handleAttachmentDrop.bind(this)}
......
......@@ -108,7 +108,7 @@ export default class ResearchPlanDetailsFieldKetcher extends Component {
return (
<div className="svg-container-static">
<SVG src={svgPath} className="molecule-mid" />
<img src={svgPath} />
</div>
)
}
......
......@@ -77,7 +77,7 @@ class ResearchPlanDetailsFieldReaction extends Component {
<Col md={12}>
{link}
<div>
<SVG src={reaction.svgPath} className="molecule-mid"/>
<img src={reaction.svgPath} />
</div>
</Col>
</Row>
......
......@@ -112,7 +112,7 @@ class ResearchPlanDetailsFieldSample extends Component {
</Col>
<Col md={8}>
<div>
<SVG src={sample.svgPath} className="molecule-mid"/>
<img src={sample.svgPath} />
</div>
</Col>
</Row>
......
const getFileName = response => {
const disposition = response.headers.get('Content-Disposition')
if (disposition && disposition.indexOf('attachment') !== -1) {
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
let matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
return matches[1].replace(/['"]/g, '');
}
}
}
const downloadBlob = (file_name, blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style = "display: none";
a.href = url;
a.download = file_name
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}
export { getFileName, downloadBlob }
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