Skip to content

Commit

Permalink
Merge branch '1084-sorting-tab-on-element-details' into 'development'
Browse files Browse the repository at this point in the history
add layout for element details

Closes #1084

See merge request ComPlat/chemotion_ELN!1348
  • Loading branch information
PiTrem committed Mar 26, 2021
2 parents 4fc4bfb + 15ad915 commit e8f0a99
Show file tree
Hide file tree
Showing 10 changed files with 558 additions and 151 deletions.
19 changes: 19 additions & 0 deletions app/api/chemotion/profile_api.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
module Chemotion

class ProfileLayoutHash < Grape::Validations::Base
def validate_param!(attr_name, params)
fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],
message: "has too many entries" if params[attr_name].keys.size > 30
params[attr_name].each do |key, val|
fail(Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],
message: "has wrong structure") unless key.to_s =~ /\A[\w \-]+\Z/
fail(Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],
message: "has wrong structure") unless val.to_s =~ /\d+/
end
end
end

class ProfileAPI < Grape::API
resource :profiles do
desc "Return the profile of the current_user"
Expand Down Expand Up @@ -29,6 +43,11 @@ class ProfileAPI < Grape::API
optional :research_plan, type: Integer
optional :wellplate, type: Integer
end
optional :layout_detail_research_plan, type: Hash, profile_layout_hash: true
optional :layout_detail_reaction, type: Hash, profile_layout_hash: true
optional :layout_detail_sample, type: Hash, profile_layout_hash: true
optional :layout_detail_wellplate, type: Hash, profile_layout_hash: true
optional :layout_detail_screen, type: Hash, profile_layout_hash: true
optional :export_selection, type: Hash do
optional :sample, type: Array[Boolean]
optional :reaction, type: Array[Boolean]
Expand Down
158 changes: 158 additions & 0 deletions app/assets/javascripts/components/ElementDetailSortTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/* eslint-disable no-param-reassign */
/* eslint-disable react/prop-types */
/* eslint-disable react/require-default-props */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button, OverlayTrigger, Popover } from 'react-bootstrap';
import Immutable from 'immutable';
import _ from 'lodash';
import UserStore from './stores/UserStore';
import UserActions from './actions/UserActions';
import TabLayoutContainer from './TabLayoutContainer';

const getNodeText = (node) => {
if (['string', 'number'].includes(typeof node)) return node;
if (node instanceof Array) return node.map(getNodeText).join('');
if (typeof node === 'object' && node) {
if (node.props.children) {
return getNodeText(node.props.children);
} else if (node.props.alt) {
return getNodeText(node.props.alt);
}
return '';
}
return '';
};

const getArrayFromLayout = (layout, availableTabs) => {
const layoutKeys = Object.keys(layout);
const enabled = availableTabs.filter(val => layoutKeys.includes(val));
const leftover = availableTabs.filter(val => !layoutKeys.includes(val));
const visible = [];
const hidden = [];

enabled.forEach((key) => {
const order = layout[key];
if (order < 0) { hidden[Math.abs(order)] = key; }
if (order > 0) { visible[order] = key; }
});

leftover.forEach(key => hidden.push(key));

let first = null;
if (visible.length === 0) {
first = hidden.filter(n => n !== undefined)[0];
if (first) {
visible.push(first);
}
}
if (hidden.length === 0) {
hidden.push('hidden');
}
return {
visible: Immutable.List(visible.filter(n => n !== undefined)),
hidden: Immutable.List(hidden.filter(n => (n !== undefined && n !== first)))
};
};


export default class ElementDetailSortTab extends Component {
constructor(props) {
super(props);
this.state = {
visible: Immutable.List(),
hidden: Immutable.List(),
};

this.type = props.type;

this.onChangeUser = this.onChangeUser.bind(this);
this.handleOnLayoutChanged = this.handleOnLayoutChanged.bind(this);

UserActions.fetchCurrentUser();
}

componentDidMount() {
UserStore.listen(this.onChangeUser);
}

componentWillUnmount() {
UserStore.unlisten(this.onChangeUser);
}

onChangeUser(state) {
const layout = (state.profile && state.profile.data && state.profile.data[`layout_detail_${this.type}`]) || {};
const { visible, hidden } = getArrayFromLayout(layout, this.props.availableTabs);

this.setState(
{ visible, hidden },
() => this.props.onTabPositionChanged(visible)
);
}

handleOnLayoutChanged() {
this.updateLayout();
}

updateLayout() {
const { visible, hidden } = this.layout.state;
const layout = {};

visible.forEach((value, index) => {
layout[value] = (index + 1);
});
hidden.filter(val => val !== 'hidden').forEach((value, index) => {
layout[value] = (-index - 1);
});

const userProfile = UserStore.getState().profile;
const layoutName = `data.layout_detail_${this.type}`;
_.set(userProfile, layoutName, layout);
UserActions.updateUserProfile(userProfile);
}

render() {
const {
visible, hidden
} = this.state;
const popoverSettings = (
<Popover
className="collection-overlay"
id="popover-layout"
style={{ maxWidth: 'none', width: 'auto' }}
>
<div>
<h3 className="popover-title">Tabs Layout</h3>
<div className="popover-content">
<TabLayoutContainer
visible={visible}
hidden={hidden}
tabTitles={this.props.tabTitles}
isElementDetails
ref={(n) => { this.layout = n; }}
/>
</div>
</div>
</Popover>
);
return (
<OverlayTrigger
trigger="click"
placement="left"
overlay={popoverSettings}
rootClose
onExit={this.handleOnLayoutChanged}
>
<Button bsStyle="info" bsSize="xsmall" className="button-right">
<i className="fa fa-sliders" aria-hidden="true" />
</Button>
</OverlayTrigger>
);
}
}

ElementDetailSortTab.propTypes = {
onTabPositionChanged: PropTypes.func,
availableTabs: PropTypes.arrayOf(PropTypes.string),
tabTitles: PropTypes.object
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export default class ReactionDescriptionEditor extends React.Component {
this.fetchPredefinedTemplates(namesToFetch);
}

componentWillUnmount() {
TextTemplateStore.unlisten(this.onChangeTemplateStore);
}

onChangeTemplateStore(state) {
const { predefinedTemplateNames, fetchedPredefinedTemplates } = state;
const { fetchedNames } = this.state;
Expand Down
137 changes: 97 additions & 40 deletions app/assets/javascripts/components/ReactionDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import { rfValueFormat } from './utils/ElementUtils';
import ExportSamplesBtn from './ExportSamplesBtn';
import CopyElementModal from './common/CopyElementModal';
import { permitOn } from './common/uis';
import UserStore from './stores/UserStore'
import UserActions from './actions/UserActions';
import Immutable from 'immutable';
import ElementDetailSortTab from './ElementDetailSortTab';
import ArrayUtils from './utils/ArrayUtils';
import KeyboardActions from './actions/KeyboardActions';
import TabLayoutContainer from './TabLayoutContainer';
import _ from 'lodash';

export default class ReactionDetails extends Component {
constructor(props) {
Expand All @@ -39,6 +47,7 @@ export default class ReactionDetails extends Component {
reaction: reaction,
literatures: reaction.literatures,
activeTab: UIStore.getState().reaction.activeTab,
visible: Immutable.List(),
};

// remarked because of #466 reaction load image issue (Paggy 12.07.2018)
Expand All @@ -49,6 +58,7 @@ export default class ReactionDetails extends Component {
this.onUIStoreChange = this.onUIStoreChange.bind(this);
this.handleReactionChange = this.handleReactionChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.onTabPositionChanged = this.onTabPositionChanged.bind(this)
}

onUIStoreChange(state) {
Expand Down Expand Up @@ -82,13 +92,14 @@ export default class ReactionDetails extends Component {
shouldComponentUpdate(nextProps, nextState) {
const nextReaction = nextProps.reaction;
const nextActiveTab = nextState.activeTab;
const { reaction, activeTab } = this.state;
const nextVisible = nextState.visible;
const { reaction, activeTab, visible } = this.state;
return (
nextReaction.id !== reaction.id ||
nextReaction.updated_at !== reaction.updated_at ||
nextReaction.reaction_svg_file !== reaction.reaction_svg_file ||
!!nextReaction.changed || !!nextReaction.editedSample ||
nextActiveTab !== activeTab
nextActiveTab !== activeTab || nextVisible !== visible
);
}

Expand Down Expand Up @@ -286,6 +297,8 @@ export default class ReactionDetails extends Component {
<ElementCollectionLabels element={reaction} key={reaction.id} placement="right" />
);



return (
<div>
<OverlayTrigger placement="bottom" overlay={<Tooltip id="sampleDates">{titleTooltip}</Tooltip>}>
Expand Down Expand Up @@ -365,56 +378,100 @@ export default class ReactionDetails extends Component {
})
}

onTabPositionChanged(visible) {
this.setState({visible})
}

render() {
const {reaction} = this.state;
const { visible } = this.state;
const tabContentsMap = {
scheme: (
<Tab eventKey="scheme" title="Scheme" key={`scheme_${reaction.id}`}>
<ReactionDetailsScheme
reaction={reaction}
onReactionChange={(reaction, options) => this.handleReactionChange(reaction, options)}
onInputChange={(type, event) => this.handleInputChange(type, event)}
/>
</Tab>
),
properties: (
<Tab eventKey="properties" title="Properties" key={`properties_${reaction.id}`}>
<ReactionDetailsProperties
reaction={reaction}
onReactionChange={r => this.handleReactionChange(r)}
onInputChange={(type, event) => this.handleInputChange(type, event)}
key={reaction.checksum}
/>
</Tab>
),
references: (
<Tab eventKey="references" title="References" key={`references_${reaction.id}`}>
<ReactionDetailsLiteratures
element={reaction}
literatures={reaction.isNew === true ? reaction.literatures : null}
onElementChange={r => this.handleReactionChange(r)}
/>
</Tab>
),
analyses: (
<Tab eventKey="analyses" title="Analyses" key={`analyses_${reaction.id}`}>
{this.productData(reaction)}
</Tab>
),
green_chemistry: (
<Tab eventKey="green_chemistry" title="Green Chemistry" key={`green_chem_${reaction.id}`}>
<GreenChemistry
reaction={reaction}
onReactionChange={this.handleReactionChange}
/>
</Tab>
)
};

const tabTitlesMap = {
green_chemistry: 'Green Chemistry'
}


for (let j = 0; j < XTabs.count; j += 1) {
if (XTabs[`on${j}`](reaction)) {
const NoName = XTabs[`content${j}`];
tabContentsMap[`xtab_${j}`] = (
<Tab eventKey={`xtab_${j}`} key={`xtab_${j}`} title={XTabs[`title${j}`]} >
<ListGroupItem style={{ paddingBottom: 20 }} >
<NoName reaction={reaction} />
</ListGroupItem>
</Tab>
);
tabTitlesMap[`xtab_${j}`] = XTabs[`title${j}`];
}
}

const tabContents = [];
visible.forEach((value) => {
const tabContent = tabContentsMap[value];
if (tabContent) { tabContents.push(tabContent); }
});

const submitLabel = (reaction && reaction.isNew) ? "Create" : "Save";
const exportButton = (reaction && reaction.isNew) ? null : <ExportSamplesBtn type="reaction" id={reaction.id} />;
let extraTabs =[];
for (let j=0;j < XTabs.count;j++){
if (XTabs['on'+j](reaction)){extraTabs.push((i)=>this.extraTab(i))}
}

const activeTab = (this.state.activeTab !== 0 && this.state.activeTab) || visible[0];
return (
<Panel className="eln-panel-detail"
bsStyle={reaction.isPendingToSave ? 'info' : 'primary'}>
<Panel.Heading>{this.reactionHeader(reaction)}</Panel.Heading>
<Panel.Body>
{this.reactionSVG(reaction)}
<Tabs activeKey={this.state.activeTab} onSelect={this.handleSelect.bind(this)}
id="reaction-detail-tab">
<Tab eventKey={0} title={'Scheme'}>
<ReactionDetailsScheme
reaction={reaction}
onReactionChange={(reaction, options) => this.handleReactionChange(reaction, options)}
onInputChange={(type, event) => this.handleInputChange(type, event)}
/>
</Tab>
<Tab eventKey={1} title={'Properties'}>
<ReactionDetailsProperties
reaction={reaction}
onReactionChange={reaction => this.handleReactionChange(reaction)}
onInputChange={(type, event) => this.handleInputChange(type, event)}
key={reaction.checksum}
/>
</Tab>
<Tab eventKey={2} title={'References'}>
<ReactionDetailsLiteratures
element={reaction}
literatures={reaction.isNew === true ? reaction.literatures : null}
onElementChange={reaction => this.handleReactionChange(reaction)}
/>
</Tab>
<Tab eventKey={3} title={'Analyses'}>
{this.productData(reaction)}
</Tab>
<Tab eventKey={4} title="Green Chemistry">
<GreenChemistry
reaction={reaction}
onReactionChange={this.handleReactionChange}
/>
</Tab>
{extraTabs.map((e,i)=>e(i))}
<ElementDetailSortTab
type="reaction"
availableTabs={Object.keys(tabContentsMap)}
tabTitles={tabTitlesMap}
onTabPositionChanged={this.onTabPositionChanged}
/>
<Tabs activeKey={activeTab} onSelect={this.handleSelect.bind(this)} id="reaction-detail-tab">
{tabContents}
</Tabs>
<hr/>
<ButtonToolbar>
Expand Down
Loading

0 comments on commit e8f0a99

Please sign in to comment.