From 698bcabd399bc9e7016ab0bab424696042afb8e5 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 23 Jul 2024 13:42:38 +0200 Subject: [PATCH 01/12] fix (UI): cannot create a single molecule --- .../mydb/elements/details/samples/SampleDetails.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js b/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js index ca85a389bd..88a7e16701 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js +++ b/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js @@ -688,7 +688,8 @@ export default class SampleDetails extends React.Component { saveBtn(sample, closeView = false) { let submitLabel = (sample && sample.isNew) ? 'Create' : 'Save'; - const hasComponents = sample.sample_type === 'Mixture' && sample.components && sample.components.length > 0; + const hasComponents = sample.sample_type !== 'Mixture' + || (sample.components && sample.components.length > 0); const isDisabled = !sample.can_update || !hasComponents; if (closeView) submitLabel += ' and close'; @@ -968,8 +969,9 @@ export default class SampleDetails extends React.Component { const timesTag = ( ); - const hasComponents = sample.sample_type === 'Mixture' && sample.components && sample.components.length > 0; - const sampleUpdateCondition = !this.sampleIsValid() || !sample.can_update || !hasComponents; + const hasComponents = sample.sample_type !== 'Mixture' + || (sample.components && sample.components.length > 0); + const sampleUpdateCondition = !this.sampleIsValid() || !sample.can_update || !hasComponents; const elementToSave = activeTab === 'inventory' ? 'Chemical' : 'Sample'; const saveAndClose = ( @@ -1420,13 +1422,13 @@ export default class SampleDetails extends React.Component { sampleAverageMW(sample) { let mw; - + if (sample.sample_type === 'Mixture' && sample.sample_details) { mw = sample.total_molecular_weight; } else { mw = sample.molecule_molecular_weight; } - + if (mw) return ; return ''; } From 52ee52427f9fcead5d4ff4e27c9874502f979d7e Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 23 Jul 2024 16:05:37 +0200 Subject: [PATCH 02/12] fix: fetching of components after fetching the sample causing the sample appear as edited --- app/packs/src/models/Sample.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/packs/src/models/Sample.js b/app/packs/src/models/Sample.js index 22e2367890..835f140ae9 100644 --- a/app/packs/src/models/Sample.js +++ b/app/packs/src/models/Sample.js @@ -1030,8 +1030,8 @@ export default class Sample extends Element { show_label: (this.decoupled && !this.molfile) ? true : (this.show_label || false), waste: this.waste, coefficient: this.coefficient, - components: this.components && this.components.length > 0 - ? this.components.map(s => s.serializeComponent()) + components: this.components && this.components.length > 0 + ? this.components.map(s => s.serializeComponent()) : [] }; _.merge(params, extra_params); @@ -1147,13 +1147,14 @@ export default class Sample extends Element { initialComponents(components) { this.components = components.sort((a, b) => a.position - b.position); + this._checksum = this.checksum(); } - async addMixtureComponent(newComponent) { + async addMixtureComponent(newComponent) { const tmpComponents = [...(this.components || [])]; const isNew = !tmpComponents.some(component => component.molecule.iupac_name === newComponent.molecule.iupac_name || component.molecule.inchikey === newComponent.molecule.inchikey - || component.molecule_cano_smiles.split('.').includes(newComponent.molecule_cano_smiles)); // check if this component is already part of a merged component (e.g. ionic compound) + || component.molecule_cano_smiles.split('.').includes(newComponent.molecule_cano_smiles)); // check if this component is already part of a merged component (e.g. ionic compound) if (!newComponent.material_group){ newComponent.material_group = 'liquid'; @@ -1178,7 +1179,7 @@ export default class Sample extends Element { } } } - + async deleteMixtureComponent(componentToDelete) { const tmpComponents = [...(this.components || [])]; const filteredComponents = tmpComponents.filter( @@ -1201,10 +1202,10 @@ export default class Sample extends Element { if (newSmiles !== this.molecule_cano_smiles ){ const result = await MoleculesFetcher.fetchBySmi(newSmiles, null, this.molfile, 'ketcher2') this.molecule = result; - this.molfile = result.molfile; + this.molfile = result.molfile; } this.setComponentPositions() - } + } updateMixtureComponentVolume(prevTotalVolume) { @@ -1212,7 +1213,7 @@ export default class Sample extends Element { return; } const totalVolume = this.amount_l; - + this.components.forEach((component) => { const purity = component.purity || 1.0; if (component.material_group === 'liquid') { @@ -1315,13 +1316,13 @@ export default class Sample extends Element { async mergeComponents(srcMat, srcGroup, tagMat, tagGroup) { const srcIndex = this.components.findIndex(mat => mat === srcMat); const tagIndex = this.components.findIndex(mat => mat === tagMat); - + if (srcIndex === -1 || tagIndex === -1) { console.error('Source or target material not found in components.'); return; } const newSmiles = `${srcMat.molecule_cano_smiles}.${tagMat.molecule_cano_smiles}`; - + try { const newMolecule = await MoleculesFetcher.fetchBySmi(newSmiles, null, this.molfile, 'ketcher2'); const newComponent = Sample.buildNew(newMolecule, this.collection_id); @@ -1330,23 +1331,23 @@ export default class Sample extends Element { await this.deleteMixtureComponent(tagMat) await this.deleteMixtureComponent(srcMat) await this.addMixtureComponent(newComponent); - + } catch (error) { console.error('Error merging components:', error); } } - + setComponentPositions() { this.components.forEach((mat, index) => { mat.position = index; }); - } + } splitSmilesToMolecule(mixtureSmiles, editor) { const promises = mixtureSmiles.map(smiles => { return MoleculesFetcher.fetchBySmi(smiles, null, null, editor); }); - + return Promise.all(promises) .then(mixtureMolecules => { return this.mixtureMoleculeToSubsample(mixtureMolecules); From b3c2ef176b69c9e3fed981904dc322194384ea1d Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Mon, 29 Jul 2024 15:59:02 +0200 Subject: [PATCH 03/12] feat: add tooltip text for the "Total volume" field for Mixture --- .../details/NumeralInputWithUnitsCompo.js | 26 ++++++++++++++++--- .../samples/propertiesTab/SampleForm.js | 9 ++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js index 8adc2cff3e..7bcf7c66ae 100644 --- a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js +++ b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js @@ -1,6 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { FormControl, ControlLabel, InputGroup, Button } from 'react-bootstrap'; +import { + FormControl, ControlLabel, InputGroup, Button, OverlayTrigger, Tooltip +} from 'react-bootstrap'; import { metPreConv, metPrefSymbols } from 'src/utilities/metricPrefix'; export default class NumeralInputWithUnitsCompo extends Component { @@ -135,7 +137,7 @@ export default class NumeralInputWithUnitsCompo extends Component { render() { const { - bsSize, bsStyle, disabled, label, unit, name + bsSize, bsStyle, disabled, label, unit, name, showInfoTooltip } = this.props; const { showString, value, metricPrefix, @@ -173,6 +175,20 @@ export default class NumeralInputWithUnitsCompo extends Component { return (
{labelWrap} + {showInfoTooltip && ( + + It is only a value given manually, i.e. volume by definition - not (re)calculated + + )} + > + + + + + )} this.handleInputDoubleClick(event)} > @@ -228,7 +244,8 @@ NumeralInputWithUnitsCompo.propTypes = { label: PropTypes.node, bsSize: PropTypes.string, bsStyle: PropTypes.string, - name: PropTypes.string + name: PropTypes.string, + showInfoTooltip: PropTypes.bool, }; NumeralInputWithUnitsCompo.defaultProps = { @@ -239,5 +256,6 @@ NumeralInputWithUnitsCompo.defaultProps = { block: false, bsSize: 'small', bsStyle: 'default', - name: '' + name: '', + showInfoTooltip: false, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js index 5edc726516..9f1d8b1b89 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js @@ -440,7 +440,8 @@ export default class SampleForm extends React.Component { disabled = false, title = '', block = false, - notApplicable = false + notApplicable = false, + showInfoTooltip = false ) { if (sample.contains_residues && unit === 'l') return false; const value = !isNaN(sample[field]) ? sample[field] : null; @@ -497,6 +498,7 @@ export default class SampleForm extends React.Component { onChange={(e) => this.handleFieldChanged(field, e)} onMetricsChange={(e) => this.handleMetricsChange(e)} id={`numInput_${field}`} + showInfoTooltip={showInfoTooltip} /> ); @@ -561,7 +563,9 @@ export default class SampleForm extends React.Component { 'l', isDisabled, '', - false + false, + false, + true )); } @@ -740,7 +744,6 @@ export default class SampleForm extends React.Component { }); } } - sampleTypeInput() { const { sample } = this.props; From 8c59c3629b3fcfff3c999beb8aad572f3e465e2f Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 6 Aug 2024 15:45:56 +0200 Subject: [PATCH 04/12] feat: add density field for the solid components in mixtures --- .../samples/propertiesTab/SampleComponent.js | 15 ++++++++------- .../propertiesTab/SampleComponentsGroup.js | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index 749512fff3..0c3b307672 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -158,8 +158,7 @@ class SampleComponent extends Component { return this.props.material; } - - materialNameWithIupac(material) { + materialNameWithIupac(material) { let materialName = ''; let moleculeIupacName = ''; const iupacStyle = { @@ -296,7 +295,7 @@ class SampleComponent extends Component { } componentMol(material, metricMol, metricPrefixesMol) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids + const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; return ( + 2 && metricPrefixes.indexOf(material.metrics[2]) > -1) ? material.metrics[2] : 'm'; @@ -491,7 +492,7 @@ class SampleComponent extends Component { {this.componentMol(material, metricMol, metricPrefixesMol)} - + {this.componentDensity(material, metricMol, metricPrefixesMol)} {this.componentConc(material, metricMolConc, metricPrefixesMolConc)} @@ -574,4 +575,4 @@ SampleComponent.propTypes = { canDrop: PropTypes.bool, isOver: PropTypes.bool, lockAmountColumn: PropTypes.bool.isRequired -}; \ No newline at end of file +}; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js index 4409c110bf..8cf3b2be1a 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js @@ -52,7 +52,8 @@ const SampleComponentsGroup = ({ concn: 'Conc.', eq: 'Ratio', ref: 'Ref', - purity: 'Purity' + purity: 'Purity', + density: 'Density', }; if (materialGroup === 'solid') { @@ -115,7 +116,7 @@ const SampleComponentsGroup = ({ } - {materialGroup === 'solid' && } + {materialGroup === 'solid' && {headers.density}} {headers.concn} {headers.purity} {headers.eq} From 0c9b13a2b3852536f73bd71edf827bd0d3dd34a3 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 6 Aug 2024 16:18:52 +0200 Subject: [PATCH 05/12] refactor: eslint warnings --- .../samples/propertiesTab/SampleComponent.js | 219 +++++++++--------- 1 file changed, 109 insertions(+), 110 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index 0c3b307672..87810c2ddd 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -16,43 +16,43 @@ import Sample from 'src/models/Sample'; import { permitCls, permitOn } from 'src/components/common/uis'; const matSource = { - beginDrag(props) { - return props; - }, + beginDrag(props) { + return props; + }, }; const matTarget = { drop(tagProps, monitor) { - const { dropSample, dropMaterial, showModalWithMaterial } = tagProps; + const { dropSample, showModalWithMaterial } = tagProps; const srcItem = monitor.getItem(); const srcType = monitor.getItemType(); - if (srcType === DragDropItemTypes.SAMPLE) { - dropSample( + if (srcType === DragDropItemTypes.SAMPLE) { + dropSample( srcItem.element, tagProps.material, tagProps.materialGroup, - ); - } else if (srcType === DragDropItemTypes.MOLECULE) { - dropSample( + ); + } else if (srcType === DragDropItemTypes.MOLECULE) { + dropSample( srcItem.element, tagProps.material, tagProps.materialGroup, null, true, - ); - } else if (srcType === DragDropItemTypes.MATERIAL) { - showModalWithMaterial( + ); + } else if (srcType === DragDropItemTypes.MATERIAL) { + showModalWithMaterial( srcItem.material, srcItem.materialGroup, tagProps.material, tagProps.materialGroup, - ); - } -}, -canDrop(tagProps, monitor) { - const srcType = monitor.getItemType(); - const isCorrectType = srcType === DragDropItemTypes.MATERIAL + ); + } + }, + canDrop(tagProps, monitor) { + const srcType = monitor.getItemType(); + const isCorrectType = srcType === DragDropItemTypes.MATERIAL || srcType === DragDropItemTypes.SAMPLE || srcType === DragDropItemTypes.MOLECULE; return isCorrectType; @@ -85,14 +85,14 @@ class SampleComponent extends Component { handleAmountChange(e, value, concType, lockColumn) { if (e.value === value) return; - + if (this.props.onChange && e) { const event = { amount: e, type: 'amountChanged', materialGroup: this.props.materialGroup, sampleID: this.componentId(), - concType: concType, + concType, updateVolume: lockColumn, }; @@ -102,7 +102,7 @@ class SampleComponent extends Component { handleDensityChange(e, value, lockColumn) { if (e.value === value) return; - + if (this.props.onChange && e) { const event = { amount: e, @@ -118,7 +118,7 @@ class SampleComponent extends Component { handlePurityChange(e, value) { if (e.value === value) return; - + if (this.props.onChange && e) { const event = { amount: e, @@ -144,12 +144,6 @@ class SampleComponent extends Component { } } - generateMolecularWeightTooltipText(sample) { - const molecularWeight = sample.decoupled ? - (sample.molecular_mass) : (sample.molecule && sample.molecule.molecular_weight); - return `molar mass: ${molecularWeight} g/mol`; - } - componentId() { return this.component().id; } @@ -159,7 +153,6 @@ class SampleComponent extends Component { } materialNameWithIupac(material) { - let materialName = ''; let moleculeIupacName = ''; const iupacStyle = { display: 'block', @@ -171,16 +164,16 @@ class SampleComponent extends Component { moleculeIupacName = material.molecule_iupac_name; - if (moleculeIupacName === '' || !moleculeIupacName ) { + if (moleculeIupacName === '' || !moleculeIupacName) { moleculeIupacName = material.molecule.sum_formular; } return ( -
- - {moleculeIupacName} - -
+
+ + {moleculeIupacName} + +
); } @@ -199,7 +192,7 @@ class SampleComponent extends Component { handleNameChange(e, value) { if (e.value === value) return; - + if (this.props.onChange && e) { const event = { newName: e.target.value, @@ -222,7 +215,7 @@ class SampleComponent extends Component { type: 'ratioChanged', sampleID: this.componentId(), materialGroup: this.props.materialGroup, - adjustAmount: adjustAmount, + adjustAmount, }; this.props.onChange(event); @@ -230,7 +223,7 @@ class SampleComponent extends Component { } handleReferenceChange(e) { - const value = e.target.value; + const { value } = e.target; if (this.props.onChange) { const event = { type: 'referenceChanged', @@ -242,6 +235,12 @@ class SampleComponent extends Component { } } + generateMolecularWeightTooltipText(sample) { + const molecularWeight = sample.decoupled + ? (sample.molecular_mass) : (sample.molecule && sample.molecule.molecular_weight); + return `molar mass: ${molecularWeight} g/mol`; + } + materialVolume(material) { if (material.contains_residues) { return notApplicableInput(); } const metricPrefixes = ['m', 'n', 'u']; @@ -258,7 +257,7 @@ class SampleComponent extends Component { metricPrefixes={metricPrefixes} precision={3} disabled={!permitOn(this.props.sample) || this.props.lockAmountColumn} - onChange={e => this.handleAmountChange(e, material.amount_l)} + onChange={(e) => this.handleAmountChange(e, material.amount_l)} onMetricsChange={this.handleMetricsChange} bsStyle={material.amount_unit === 'l' ? 'success' : 'default'} /> @@ -273,47 +272,48 @@ class SampleComponent extends Component { delay="100" placement="top" overlay={ - {this.generateMolecularWeightTooltipText(material)} - }> -
- this.handleAmountChange(e, material.amount_g)} - onMetricsChange={this.handleMetricsChange} - bsStyle={material.error_mass ? 'error' : massBsStyle} - name="molecular-weight" - /> -
- - ) + {this.generateMolecularWeightTooltipText(material)} + } + > +
+ this.handleAmountChange(e, material.amount_g)} + onMetricsChange={this.handleMetricsChange} + bsStyle={material.error_mass ? 'error' : massBsStyle} + name="molecular-weight" + /> +
+ + ); } componentMol(material, metricMol, metricPrefixesMol) { const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; return ( this.handleAmountChange(e, material.amount_mol)} - onMetricsChange={this.handleMetricsChange} - bsStyle={material.amount_unit === 'mol' ? 'success' : 'default'} - /> - ) + key={material.id} + value={material.amount_mol} + unit="mol" + metricPrefix={metricMol} + metricPrefixes={metricPrefixesMol} + precision={4} + disabled={!permitOn(this.props.sample) || lockColumn} + onChange={(e) => this.handleAmountChange(e, material.amount_mol)} + onMetricsChange={this.handleMetricsChange} + bsStyle={material.amount_unit === 'mol' ? 'success' : 'default'} + /> + ); } componentConc(material, metricMolConc, metricPrefixesMolConc) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids + const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; return ( this.handleAmountChange(e, material.concn, 'targetConc', lockColumn)} + onChange={(e) => this.handleAmountChange(e, material.concn, 'targetConc', lockColumn)} onMetricsChange={this.handleMetricsChange} /> - ) + ); } componentStartingConc(material, metricMolConc, metricPrefixesMolConc) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids + const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; return ( this.handleAmountChange(e, material.startingConc, 'startingConc', lockColumn)} + onChange={(e) => this.handleAmountChange(e, material.startingConc, 'startingConc', lockColumn)} onMetricsChange={this.handleMetricsChange} /> - ) + ); } materialRef(material) { return ( - this.handleReferenceChange(e)} - bsSize="xsmall" - style={{ margin: 0 }} - /> + this.handleReferenceChange(e)} + bsSize="xsmall" + style={{ margin: 0 }} + /> ); } componentDensity(material) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids + const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; return ( this.handleDensityChange(e, material.density, lockColumn)} + onChange={(e) => this.handleDensityChange(e, material.density, lockColumn)} /> - ) + ); } mixtureComponent(props, style) { @@ -423,13 +423,12 @@ class SampleComponent extends Component { {this.componentConc(material, metricMolConc, metricPrefixesMolConc)} - this.handlePurityChange(e, material.purity)} + onChange={(e) => this.handlePurityChange(e, material.purity)} /> @@ -438,7 +437,7 @@ class SampleComponent extends Component { precision={4} value={material.equivalent} disabled={!permitOn(this.props.sample) || material.reference} - onChange={e => this.handleRatioChange(e, material.equivalent)} + onChange={(e) => this.handleRatioChange(e, material.equivalent)} /> @@ -457,7 +456,9 @@ class SampleComponent extends Component { } solidComponent(props, style) { - const { sample, material, deleteMaterial, connectDragSource, connectDropTarget } = props; + const { + sample, material, deleteMaterial, connectDragSource, connectDropTarget + } = props; const metricPrefixes = ['m', 'n', 'u']; const metric = (material.metrics && material.metrics.length > 2 && metricPrefixes.indexOf(material.metrics[0]) > -1) ? material.metrics[0] : 'm'; const metricPrefixesMol = ['m', 'n']; @@ -484,7 +485,7 @@ class SampleComponent extends Component { {this.materialRef(material)} - + {this.componentMass(material, metric, metricPrefixes, massBsStyle)} @@ -500,7 +501,7 @@ class SampleComponent extends Component { precision={4} value={material.purity} disabled={!permitOn(this.props.sample)} - onChange={e => this.handlePurityChange(e, material.purity)} + onChange={(e) => this.handlePurityChange(e, material.purity)} /> @@ -509,7 +510,7 @@ class SampleComponent extends Component { precision={4} value={material.equivalent} disabled={!permitOn(this.props.sample) || material.reference} - onChange={e => this.handleRatioChange(e, material.equivalent)} + onChange={(e) => this.handleRatioChange(e, material.equivalent)} /> @@ -545,26 +546,24 @@ class SampleComponent extends Component { if (material.material_group === 'liquid') { return this.mixtureComponent(this.props, style); - } else { - return this.solidComponent(this.props, style); } + return this.solidComponent(this.props, style); } } - export default compose( - DragSource( - DragDropItemTypes.MATERIAL, - matSource, - matSrcCollect, - ), - DropTarget( - [DragDropItemTypes.SAMPLE, DragDropItemTypes.MOLECULE, DragDropItemTypes.MATERIAL], - matTarget, - matTagCollect, - ), - )(SampleComponent); - + DragSource( + DragDropItemTypes.MATERIAL, + matSource, + matSrcCollect, + ), + DropTarget( + [DragDropItemTypes.SAMPLE, DragDropItemTypes.MOLECULE, DragDropItemTypes.MATERIAL], + matTarget, + matTagCollect, + ), +)(SampleComponent); + SampleComponent.propTypes = { material: PropTypes.instanceOf(Sample).isRequired, materialGroup: PropTypes.string.isRequired, From 36c0fae63b8274a7214f3608104870071751dafb Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 6 Aug 2024 16:18:52 +0200 Subject: [PATCH 06/12] refactor: eslint warnings --- .../propertiesTab/SampleDetailsComponents.js | 215 +++++----- app/packs/src/models/Component.js | 384 +++++++++--------- 2 files changed, 300 insertions(+), 299 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js index 137e8a38d2..540678c50d 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js @@ -39,12 +39,33 @@ export default class SampleDetailsComponents extends React.Component { this.updatePurity = this.updatePurity.bind(this); } + handleModalClose() { + this.setState({ showModal: false, droppedMaterial: null }); + } + + handleModalAction(action) { + const { droppedMaterial, sample } = this.state; + + if (droppedMaterial) { + const { + srcMat, srcGroup, tagMat, tagGroup + } = droppedMaterial; + this.dropMaterial(srcMat, srcGroup, tagMat, tagGroup, action); + } + this.handleModalClose(); + this.props.onChange(sample); + } + + handleTabSelect(tab) { + this.setState({ activeTab: tab }); + } + onChangeComponent(changeEvent) { const { sample } = this.state; sample.components = sample.components.map((component) => { if (!(component instanceof Component)) { - return new Component(component) + return new Component(component); } return component; }); @@ -67,7 +88,7 @@ export default class SampleDetailsComponents extends React.Component { break; case 'purityChanged': this.updatePurity(changeEvent); - break; + break; case 'densityChanged': this.updateDensity(changeEvent); break; @@ -76,11 +97,12 @@ export default class SampleDetailsComponents extends React.Component { } this.props.onChange(sample); } - - + updatedSampleForAmountUnitChange(changeEvent) { const { sample } = this.props; - const { sampleID, amount, concType, updateVolume } = changeEvent; + const { + sampleID, amount, concType, updateVolume + } = changeEvent; const componentIndex = this.props.sample.components.findIndex( (component) => component.id === sampleID ); @@ -88,12 +110,12 @@ export default class SampleDetailsComponents extends React.Component { const totalVolume = sample.amount_l; if (amount.unit === 'g' || amount.unit === 'l') { - sample.components[componentIndex].setAmount(amount, totalVolume) - } else if (amount.unit === 'mol/l' ) { - sample.components[componentIndex].setConc(amount, totalVolume, concType, updateVolume) - } + sample.components[componentIndex].setAmount(amount, totalVolume); + } else if (amount.unit === 'mol/l') { + sample.components[componentIndex].setConc(amount, totalVolume, concType, updateVolume); + } // update components ratio - sample.updateMixtureComponentEquivalent() + sample.updateMixtureComponentEquivalent(); } updateDensity(changeEvent) { @@ -105,9 +127,9 @@ export default class SampleDetailsComponents extends React.Component { const totalVolume = sample.amount_l; - sample.components[componentIndex].setDensity(amount, updateVolume, totalVolume) + sample.components[componentIndex].setDensity(amount, updateVolume, totalVolume); // update components ratio - sample.updateMixtureComponentEquivalent() + sample.updateMixtureComponentEquivalent(); } updatePurity(changeEvent) { @@ -118,9 +140,9 @@ export default class SampleDetailsComponents extends React.Component { (component) => component.id === sampleID ); sample.components[componentIndex].setPurity(purity, sample.amount_l); - sample.updateMixtureComponentEquivalent() + sample.updateMixtureComponentEquivalent(); } - + updatedSampleForMetricsChange(changeEvent) { const { sample } = this.props; const { sampleID, metricUnit, metricPrefix } = changeEvent; @@ -132,58 +154,57 @@ export default class SampleDetailsComponents extends React.Component { dropSample(srcSample, tagMaterial, tagGroup, extLabel, isNewSample = false) { const { sample } = this.state; - const { currentCollection } = UIStore.getState() + const { currentCollection } = UIStore.getState(); let splitSample; if (srcSample instanceof Molecule || isNewSample) { splitSample = Sample.buildNew(srcSample, currentCollection.id); - splitSample = new Component(splitSample) + splitSample = new Component(splitSample); } else if (srcSample instanceof Sample) { splitSample = srcSample.buildChildWithoutCounter(); - splitSample = new Component(splitSample) + splitSample = new Component(splitSample); } splitSample.material_group = tagGroup; if (splitSample.sample_type === 'Mixture') { ComponentsFetcher.fetchComponentsBySampleId(srcSample.id) - .then(async components => { - for (const component of components) { - const { component_properties, ...rest } = component; - const sampleData = { - ...rest, - ...component_properties - }; - let sampleComponent = new Component(sampleData); - sampleComponent.parent_id = splitSample.parent_id - sampleComponent.material_group = tagGroup; - sampleComponent.reference = false; - if (tagGroup === 'solid') { - sampleComponent.setMolarity({ value: 0, unit: 'M' }, sample.amount_l, 'startingConc'); - sampleComponent.setAmount({ value: sampleComponent.amount_g, unit: 'g' }, sample.amount_l); - } else if (tagGroup === 'liquid') { - sampleComponent.setAmount({ value: sampleComponent.amount_l, unit: 'l' }, sample.amount_l); + .then(async (components) => { + for (const component of components) { + const { component_properties, ...rest } = component; + const sampleData = { + ...rest, + ...component_properties + }; + const sampleComponent = new Component(sampleData); + sampleComponent.parent_id = splitSample.parent_id; + sampleComponent.material_group = tagGroup; + sampleComponent.reference = false; + if (tagGroup === 'solid') { + sampleComponent.setMolarity({ value: 0, unit: 'M' }, sample.amount_l, 'startingConc'); + sampleComponent.setAmount({ value: sampleComponent.amount_g, unit: 'g' }, sample.amount_l); + } else if (tagGroup === 'liquid') { + sampleComponent.setAmount({ value: sampleComponent.amount_l, unit: 'l' }, sample.amount_l); + } + sampleComponent.id = `comp_${Math.random().toString(36).substr(2, 9)}`; + await sample.addMixtureComponent(sampleComponent); + sample.updateMixtureComponentEquivalent(); } - sampleComponent.id = `comp_${Math.random().toString(36).substr(2, 9)}` - await sample.addMixtureComponent(sampleComponent); - sample.updateMixtureComponentEquivalent() - } - this.props.onChange(sample); - }) - .catch(errorMessage => { + this.props.onChange(sample); + }) + .catch((errorMessage) => { console.error(errorMessage); }); } else { sample.addMixtureComponent(splitSample); - sample.updateMixtureComponentEquivalent() + sample.updateMixtureComponentEquivalent(); this.props.onChange(sample); } } updateComponentName(changeEvent) { const { sample } = this.props; - const sampleID = changeEvent.sampleID; - const newName = changeEvent.newName; + const { sampleID, newName } = changeEvent; const componentIndex = this.props.sample.components.findIndex( (component) => component.id === sampleID ); @@ -196,7 +217,7 @@ export default class SampleDetailsComponents extends React.Component { const { sample } = this.state; sample.components = sample.components.map((component) => { if (!(component instanceof Component)) { - return new Component(component) + return new Component(component); } return component; }); @@ -206,16 +227,15 @@ export default class SampleDetailsComponents extends React.Component { this.props.onChange(sample); } else if (action === 'merge') { sample.mergeComponents(srcMat, srcGroup, tagMat, tagGroup) - .then(() => { - this.props.onChange(sample); - }) - .catch((error) => { - console.error('Error merging components:', error); - }); + .then(() => { + this.props.onChange(sample); + }) + .catch((error) => { + console.error('Error merging components:', error); + }); } } - deleteMixtureComponent(component) { const { sample } = this.state; sample.deleteMixtureComponent(component); @@ -231,13 +251,11 @@ export default class SampleDetailsComponents extends React.Component { } } - handleTabSelect(tab) { - this.setState({ activeTab: tab }); - } - updateRatio(changeEvent) { const { sample } = this.props; - const { sampleID, newRatio, materialGroup, adjustAmount } = changeEvent; + const { + sampleID, newRatio, materialGroup + } = changeEvent; const componentIndex = this.props.sample.components.findIndex( (component) => component.id === sampleID ); @@ -247,7 +265,7 @@ export default class SampleDetailsComponents extends React.Component { const referenceMoles = sample.components[refIndex].amount_mol; const totalVolume = sample.amount_l; - sample.components[componentIndex].updateRatio(newRatio, materialGroup, totalVolume, referenceMoles) + sample.components[componentIndex].updateRatio(newRatio, materialGroup, totalVolume, referenceMoles); sample.updateMixtureMolecularWeight(); @@ -261,7 +279,6 @@ export default class SampleDetailsComponents extends React.Component { (component) => component.id === sampleID ); - sample.setReferenceComponent(componentIndex); } @@ -275,30 +292,16 @@ export default class SampleDetailsComponents extends React.Component { } this.setState({ showModal: true, - droppedMaterial: { srcMat, srcGroup, tagMat, tagGroup }, + droppedMaterial: { + srcMat, srcGroup, tagMat, tagGroup + }, }); } - handleModalClose() { - this.setState({ showModal: false, droppedMaterial: null }); - } - - handleModalAction(action) { - const { droppedMaterial, sample } = this.state; - - if (droppedMaterial) { - const { srcMat, srcGroup, tagMat, tagGroup } = droppedMaterial; - this.dropMaterial(srcMat, srcGroup, tagMat, tagGroup, action); - } - this.handleModalClose(); - this.props.onChange(sample); - } - renderModal() { return ( - - +

Do you want to merge or move this component?

@@ -327,16 +330,14 @@ export default class SampleDetailsComponents extends React.Component { const minPadding = { padding: '1px 2px 2px 0px' }; if (sample && sample.components) { - sample.components = sample.components.map(component => - component instanceof Component ? component : new Component(component) - ); + sample.components = sample.components.map((component) => (component instanceof Component ? component : new Component(component))); } const liquids = sample.components - ? sample.components.filter(component => component.material_group === 'liquid') + ? sample.components.filter((component) => component.material_group === 'liquid') : []; const solids = sample.components - ? sample.components.filter(component => component.material_group === 'solid') + ? sample.components.filter((component) => component.material_group === 'solid') : []; return ( @@ -344,36 +345,36 @@ export default class SampleDetailsComponents extends React.Component { {this.renderModal()} this.onChangeComponent(changeEvent)} - switchAmount={this.switchAmount} - lockAmountColumn={this.state.lockAmountColumn} - lockAmountColumnSolids={this.state.lockAmountColumnSolids} - materialGroup="liquid" - showModalWithMaterial={this.showModalWithMaterial} - handleTabSelect={this.handleTabSelect} - activeTab={this.state.activeTab} + sample={sample} + sampleComponents={liquids} + dropSample={this.dropSample} + dropMaterial={this.dropMaterial} + deleteMixtureComponent={this.deleteMixtureComponent} + onChangeComponent={(changeEvent) => this.onChangeComponent(changeEvent)} + switchAmount={this.switchAmount} + lockAmountColumn={this.state.lockAmountColumn} + lockAmountColumnSolids={this.state.lockAmountColumnSolids} + materialGroup="liquid" + showModalWithMaterial={this.showModalWithMaterial} + handleTabSelect={this.handleTabSelect} + activeTab={this.state.activeTab} /> this.onChangeComponent(changeEvent)} - switchAmount={this.switchAmount} - lockAmountColumn={this.state.lockAmountColumn} - lockAmountColumnSolids={this.state.lockAmountColumnSolids} - materialGroup="solid" - showModalWithMaterial={this.showModalWithMaterial} - handleTabSelect={this.handleTabSelect} - activeTab={this.state.activeTab} + sample={sample} + sampleComponents={solids} + dropSample={this.dropSample} + dropMaterial={this.dropMaterial} + deleteMixtureComponent={this.deleteMixtureComponent} + onChangeComponent={(changeEvent) => this.onChangeComponent(changeEvent)} + switchAmount={this.switchAmount} + lockAmountColumn={this.state.lockAmountColumn} + lockAmountColumnSolids={this.state.lockAmountColumnSolids} + materialGroup="solid" + showModalWithMaterial={this.showModalWithMaterial} + handleTabSelect={this.handleTabSelect} + activeTab={this.state.activeTab} /> diff --git a/app/packs/src/models/Component.js b/app/packs/src/models/Component.js index e0e4b13eae..e11f63b4ce 100644 --- a/app/packs/src/models/Component.js +++ b/app/packs/src/models/Component.js @@ -1,202 +1,202 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable camelcase */ import React from 'react'; -import _ from 'lodash'; import Sample from 'src/models/Sample'; - export default class Component extends Sample { - constructor(props) { - super(props); - } - - get has_density() { - return this.density > 0 && this.starting_molarity_value === 0; - } - - get amount_mol() { - return this._amount_mol - } - - set amount_mol(amount_mol) { - this._amount_mol = amount_mol; - } - - get amount_g() { - return this._amount_g; - } - set amount_g(amount_g) { - return this._amount_g = amount_g; - } - - get amount_l() { - return this._amount_l - } - set amount_l(amount_l) { - return this._amount_l = amount_l; - } - - setAmount(amount, totalVolume) { - if (!amount.unit || isNaN(amount.value)) { return } - if (this.density && this.density > 0 && this.material_group !== 'solid') { - this.setAmountDensity(amount, totalVolume) - } else { - this.setAmountConc(amount, totalVolume) - } - } - - setAmountDensity(amount, totalVolume) { - this.amount_l = amount.value; - this.starting_molarity_value = 0; - const purity = this.purity || 1.0; - if (this.material_group === 'liquid') { - this.amount_g = (this.amount_l * 1000) * this.density; - this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - } - } - - setAmountConc(amount, totalVolume) { - const purity = this.purity || 1.0; - if (amount.unit === 'l') { - this.amount_l = amount.value; - this.amount_mol = this.starting_molarity_value * this.amount_l * purity - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } else if (amount.unit === 'g') { - this.amount_g = amount.value; - this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; - if (totalVolume) { - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } - - if (this.amount_l === 0 || this.amount_g === 0) { - this.molarity_value = this.concn = 0; - } - } - - this.density = 0; - } - - setConc(amount, totalVolume, concType, updateVolume) { - if (!amount.unit || isNaN(amount.value) || amount.unit !== 'mol/l') { return } - - if (this.density && this.density > 0 && concType !== 'startingConc' && this.material_group !== 'solid') { - this.setMolarityDensity(amount, totalVolume) - } else { - this.setMolarity(amount, totalVolume, concType, updateVolume) - } - } - - setMolarityDensity(amount, totalVolume) { - const purity = this.purity || 1.0; - this.molarity_value = this.concn = amount.value; - this.molarity_unit = amount.unit; - this.starting_molarity_value = 0; - - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = (this.molecule_molecular_weight * this.amount_mol) / purity - this.amount_l = (this.amount_g / this.density) / 1000; - } - - setMolarity(amount, totalVolume, concType, updateVolume) { - const purity = this.purity || 1.0; - if (concType !== 'startingConc') { - this.concn = amount.value; - this.molarity_value = amount.value; - this.molarity_unit = amount.unit - } else { - this.starting_molarity_value = amount.value; - this.starting_molarity_unit = amount.unit; - } - if (this.material_group === 'liquid' && updateVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); - } else if (this.material_group === 'liquid' && !updateVolume) { - this.amount_mol = this.starting_molarity_value * this.amount_l * purity - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); - } else if (this.material_group === 'solid' && this.concn && totalVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = this.molecule_molecular_weight * (this.amount_mol / purity); - } else if (this.concn === 0) { - this.amount_g = 0; - this.amount_l = 0; - } - - this.density = 0; - } - - setDensity(density, updateVolume, totalVolume) { - const purity = this.purity || 1.0; - if (!density.unit || isNaN(density.value) || density.unit !== 'g/ml') { return } - - this.density = density.value; - this.starting_molarity_value = 0; - - if (updateVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = this.amount_mol * this.molecule_molecular_weight / purity; - this.amount_l = (this.amount_g / this.density) / 1000; - } else { - this.amount_g = (this.amount_l * 1000) * this.density; - this.amount_mol = this.amount_g * purity / this.molecule_molecular_weight; - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); - } - } + constructor(props) { + super(props); + } + + get has_density() { + return this.density > 0 && this.starting_molarity_value === 0; + } + + get amount_mol() { + return this._amount_mol; + } + + set amount_mol(amount_mol) { + this._amount_mol = amount_mol; + } + + get amount_g() { + return this._amount_g; + } + + set amount_g(amount_g) { + return this._amount_g = amount_g; + } + + get amount_l() { + return this._amount_l; + } + + set amount_l(amount_l) { + return this._amount_l = amount_l; + } + + setAmount(amount, totalVolume) { + if (!amount.unit || isNaN(amount.value)) { return; } + if (this.density && this.density > 0 && this.material_group !== 'solid') { + this.setAmountDensity(amount, totalVolume); + } else { + this.setAmountConc(amount, totalVolume); + } + } + + setAmountDensity(amount, totalVolume) { + this.amount_l = amount.value; + this.starting_molarity_value = 0; + const purity = this.purity || 1.0; + if (this.material_group === 'liquid') { + this.amount_g = (this.amount_l * 1000) * this.density; + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + } + } + + setAmountConc(amount, totalVolume) { + const purity = this.purity || 1.0; + if (amount.unit === 'l') { + this.amount_l = amount.value; + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } else if (amount.unit === 'g') { + this.amount_g = amount.value; + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; + if (totalVolume) { + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } - updateRatio(newRatio, materialGroup, totalVolume, referenceMoles) { - if (this.equivalent === newRatio) { return } - - const purity = this.purity || 1.0; - this.amount_mol = newRatio * referenceMoles; - this.equivalent = newRatio; - - if (materialGroup === 'liquid') { - if (!this.has_density) { - this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } else if (this.has_density) { - this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; - this.amount_l = this.amount_g / (this.density * 1000); - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } - } else if (materialGroup === 'solid') { - this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - } + if (this.amount_l === 0 || this.amount_g === 0) { + this.molarity_value = this.concn = 0; + } } - setPurity(purity, totalVolume) { - if (!isNaN(purity) && purity >= 0 && purity <= 1) { - this.purity = purity; - this.amount_mol = this.molarity_value * totalVolume * this.purity; - } - } - - serializeComponent() { - return { - id: this.id, - name: this.name, - position: this.position, - component_properties: { - amount_mol: this.amount_mol, - amount_l: this.amount_l, - amount_g: this.amount_g, - density: this.density, - molarity_unit: this.molarity_unit, - molarity_value: this.molarity_value, - starting_molarity_value: this.starting_molarity_value, - starting_molarity_unit: this.starting_molarity_unit, - molecule_id: this.molecule.id, - equivalent: this.equivalent, - parent_id: this.parent_id, - material_group: this.material_group, - reference: this.reference, - purity: this.purity, - } - } + this.density = 0; + } + + setConc(amount, totalVolume, concType, updateVolume) { + if (!amount.unit || isNaN(amount.value) || amount.unit !== 'mol/l') { return; } + + if (this.density && this.density > 0 && concType !== 'startingConc' && this.material_group !== 'solid') { + this.setMolarityDensity(amount, totalVolume); + } else { + this.setMolarity(amount, totalVolume, concType, updateVolume); + } + } + + setMolarityDensity(amount, totalVolume) { + const purity = this.purity || 1.0; + this.molarity_value = this.concn = amount.value; + this.molarity_unit = amount.unit; + this.starting_molarity_value = 0; + + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = (this.molecule_molecular_weight * this.amount_mol) / purity; + this.amount_l = (this.amount_g / this.density) / 1000; + } + + setMolarity(amount, totalVolume, concType, updateVolume) { + const purity = this.purity || 1.0; + if (concType !== 'startingConc') { + this.concn = amount.value; + this.molarity_value = amount.value; + this.molarity_unit = amount.unit; + } else { + this.starting_molarity_value = amount.value; + this.starting_molarity_unit = amount.unit; + } + if (this.material_group === 'liquid' && updateVolume) { + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + } else if (this.material_group === 'liquid' && !updateVolume) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + } else if (this.material_group === 'solid' && this.concn && totalVolume) { + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = this.molecule_molecular_weight * (this.amount_mol / purity); + } else if (this.concn === 0) { + this.amount_g = 0; + this.amount_l = 0; + } + + this.density = 0; + } + + setDensity(density, updateVolume, totalVolume) { + const purity = this.purity || 1.0; + if (!density.unit || isNaN(density.value) || density.unit !== 'g/ml') { return; } + + this.density = density.value; + this.starting_molarity_value = 0; + + if (updateVolume) { + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = this.amount_mol * this.molecule_molecular_weight / purity; + this.amount_l = (this.amount_g / this.density) / 1000; + } else { + this.amount_g = (this.amount_l * 1000) * this.density; + this.amount_mol = this.amount_g * purity / this.molecule_molecular_weight; + this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + } + } + + updateRatio(newRatio, materialGroup, totalVolume, referenceMoles) { + if (this.equivalent === newRatio) { return; } + + const purity = this.purity || 1.0; + this.amount_mol = newRatio * referenceMoles; + this.equivalent = newRatio; + + if (materialGroup === 'liquid') { + if (!this.has_density) { + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } else if (this.has_density) { + this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; + this.amount_l = this.amount_g / (this.density * 1000); + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } + } else if (materialGroup === 'solid') { + this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + } + } + + setPurity(purity, totalVolume) { + if (!isNaN(purity) && purity >= 0 && purity <= 1) { + this.purity = purity; + this.amount_mol = this.molarity_value * totalVolume * this.purity; + } + } + + serializeComponent() { + return { + id: this.id, + name: this.name, + position: this.position, + component_properties: { + amount_mol: this.amount_mol, + amount_l: this.amount_l, + amount_g: this.amount_g, + density: this.density, + molarity_unit: this.molarity_unit, + molarity_value: this.molarity_value, + starting_molarity_value: this.starting_molarity_value, + starting_molarity_unit: this.starting_molarity_unit, + molecule_id: this.molecule.id, + equivalent: this.equivalent, + parent_id: this.parent_id, + material_group: this.material_group, + reference: this.reference, + purity: this.purity, } -} \ No newline at end of file + }; + } +} From 13cdb86f09e527e74e2b7aa861b408d0b19a15b0 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Wed, 7 Aug 2024 15:13:08 +0200 Subject: [PATCH 07/12] feat: add the preview image to component in mixtures --- .../samples/propertiesTab/SampleComponent.js | 25 +++++++++++++++++-- app/packs/src/models/Component.js | 5 ++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index 87810c2ddd..bf3bf48d7e 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -14,6 +14,7 @@ import { DragDropItemTypes } from 'src/utilities/DndConst'; import NumeralInputWithUnitsCompo from 'src/apps/mydb/elements/details/NumeralInputWithUnitsCompo'; import Sample from 'src/models/Sample'; import { permitCls, permitOn } from 'src/components/common/uis'; +import SvgWithPopover from 'src/components/common/SvgWithPopover'; const matSource = { beginDrag(props) { @@ -171,7 +172,7 @@ class SampleComponent extends Component { return (
- {moleculeIupacName} + {this.svgPreview(material, moleculeIupacName)}
); @@ -402,7 +403,7 @@ class SampleComponent extends Component { { dropEffect: 'copy' } )} - + {this.materialNameWithIupac(material)} @@ -528,6 +529,26 @@ class SampleComponent extends Component { ); } + svgPreview(material, moleculeIupacName) { + return ( + + ); + } + render() { const { isDragging, canDrop, isOver, material diff --git a/app/packs/src/models/Component.js b/app/packs/src/models/Component.js index e11f63b4ce..6d21b64cda 100644 --- a/app/packs/src/models/Component.js +++ b/app/packs/src/models/Component.js @@ -36,6 +36,11 @@ export default class Component extends Sample { return this._amount_l = amount_l; } + get svgPath() { + return this.molecule && this.molecule.molecule_svg_file + ? `/images/molecules/${this.molecule.molecule_svg_file}` : ''; + } + setAmount(amount, totalVolume) { if (!amount.unit || isNaN(amount.value)) { return; } if (this.density && this.density > 0 && this.material_group !== 'solid') { From 9922e1b564ff126c1795f37bc068915608d18326 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Wed, 7 Aug 2024 16:37:53 +0200 Subject: [PATCH 08/12] fix: issue with the Amount field not being set for mixture components feat: calculate other fields based on the Amount field fix: the Volume column being updated though it has been locked --- .../samples/propertiesTab/SampleComponent.js | 2 +- .../propertiesTab/SampleDetailsComponents.js | 8 ++-- app/packs/src/models/Component.js | 47 ++++++++++++++++++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index bf3bf48d7e..4077067aa9 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -94,7 +94,7 @@ class SampleComponent extends Component { materialGroup: this.props.materialGroup, sampleID: this.componentId(), concType, - updateVolume: lockColumn, + updateVolume: !lockColumn, }; this.props.onChange(event); diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js index 540678c50d..15946a4cbd 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js @@ -20,6 +20,8 @@ export default class SampleDetailsComponents extends React.Component { showModal: false, droppedMaterial: null, activeTab: 'concentration', + lockAmountColumn: false, + lockAmountColumnSolids: false, }; this.dropSample = this.dropSample.bind(this); @@ -100,9 +102,7 @@ export default class SampleDetailsComponents extends React.Component { updatedSampleForAmountUnitChange(changeEvent) { const { sample } = this.props; - const { - sampleID, amount, concType, updateVolume - } = changeEvent; + const {sampleID, amount, concType, updateVolume} = changeEvent; const componentIndex = this.props.sample.components.findIndex( (component) => component.id === sampleID ); @@ -111,6 +111,8 @@ export default class SampleDetailsComponents extends React.Component { if (amount.unit === 'g' || amount.unit === 'l') { sample.components[componentIndex].setAmount(amount, totalVolume); + } else if (amount.unit === 'mol') { + sample.components[componentIndex].setMol(amount, totalVolume); } else if (amount.unit === 'mol/l') { sample.components[componentIndex].setConc(amount, totalVolume, concType, updateVolume); } diff --git a/app/packs/src/models/Component.js b/app/packs/src/models/Component.js index 6d21b64cda..ef57c3bdc0 100644 --- a/app/packs/src/models/Component.js +++ b/app/packs/src/models/Component.js @@ -42,7 +42,9 @@ export default class Component extends Sample { } setAmount(amount, totalVolume) { - if (!amount.unit || isNaN(amount.value)) { return; } + if (!amount.unit || Number.isNaN(amount.value)) { + return; + } if (this.density && this.density > 0 && this.material_group !== 'solid') { this.setAmountDensity(amount, totalVolume); } else { @@ -50,6 +52,40 @@ export default class Component extends Sample { } } + // refactor this part + setMol(amount, totalVolume) { + if (Number.isNaN(amount.value) || amount.unit !== 'mol') return; + + this.amount_mol = amount.value; + const purity = this.purity || 1.0; + + if (this.density && this.density > 0 && this.material_group === 'liquid') { + // update density + this.starting_molarity_value = 0; + + this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / this.purity; + + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; + } + } else if (this.material_group === 'solid') { + // update concentrations + if (this.amount_l === 0 || this.amount_g === 0) { + this.molarity_value = 0; + this.concn = 0; + } else if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + + this.concn = concentration; + this.molarity_value = concentration; + + this.molarity_unit = 'M'; + } + } + } + setAmountDensity(amount, totalVolume) { this.amount_l = amount.value; this.starting_molarity_value = 0; @@ -65,7 +101,9 @@ export default class Component extends Sample { const purity = this.purity || 1.0; if (amount.unit === 'l') { this.amount_l = amount.value; - this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + if (this.starting_molarity_value) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + } this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); this.molarity_unit = 'M'; } else if (amount.unit === 'g') { @@ -181,6 +219,11 @@ export default class Component extends Sample { } } + get svgPath() { + return this.molecule && this.molecule.molecule_svg_file + ? `/images/molecules/${this.molecule.molecule_svg_file}` : ''; + } + serializeComponent() { return { id: this.id, From cfd66da5cd54845336a51fd731107d8c8afb5b02 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Thu, 8 Aug 2024 16:11:52 +0200 Subject: [PATCH 09/12] fix: component table header updated feat: add tooltip text for total conc. refactor: eslint warnings --- .../details/NumeralInputWithUnitsCompo.js | 12 +- .../samples/propertiesTab/SampleComponent.js | 10 +- .../propertiesTab/SampleComponentsGroup.js | 270 ++++++++++-------- .../samples/propertiesTab/SampleForm.js | 4 +- 4 files changed, 162 insertions(+), 134 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js index 7bcf7c66ae..0c55ae47bb 100644 --- a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js +++ b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js @@ -137,7 +137,7 @@ export default class NumeralInputWithUnitsCompo extends Component { render() { const { - bsSize, bsStyle, disabled, label, unit, name, showInfoTooltip + bsSize, bsStyle, disabled, label, unit, name, showInfoTooltipTotalVol } = this.props; const { showString, value, metricPrefix, @@ -175,7 +175,7 @@ export default class NumeralInputWithUnitsCompo extends Component { return (
{labelWrap} - {showInfoTooltip && ( + {showInfoTooltipTotalVol && ( )} > - - + + )} @@ -245,7 +245,7 @@ NumeralInputWithUnitsCompo.propTypes = { bsSize: PropTypes.string, bsStyle: PropTypes.string, name: PropTypes.string, - showInfoTooltip: PropTypes.bool, + showInfoTooltipTotalVol: PropTypes.bool, }; NumeralInputWithUnitsCompo.defaultProps = { @@ -257,5 +257,5 @@ NumeralInputWithUnitsCompo.defaultProps = { bsSize: 'small', bsStyle: 'default', name: '', - showInfoTooltip: false, + showInfoTooltipTotalVol: false, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index 4077067aa9..251f8d80d0 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -314,7 +314,10 @@ class SampleComponent extends Component { } componentConc(material, metricMolConc, metricPrefixesMolConc) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; + const { + materialGroup, lockAmountColumn, lockAmountColumnSolids, sample + } = this.props; + const lockColumn = materialGroup === 'liquid' ? lockAmountColumn : lockAmountColumnSolids; return ( this.handleAmountChange(e, material.concn, 'targetConc', lockColumn)} onMetricsChange={this.handleMetricsChange} /> @@ -594,5 +597,6 @@ SampleComponent.propTypes = { isDragging: PropTypes.bool, canDrop: PropTypes.bool, isOver: PropTypes.bool, - lockAmountColumn: PropTypes.bool.isRequired + lockAmountColumn: PropTypes.bool.isRequired, + lockAmountColumnSolids: PropTypes.bool.isRequired, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js index 8cf3b2be1a..01a7e898f5 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js @@ -1,90 +1,91 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Button, OverlayTrigger, Tooltip, Tab, Tabs } from 'react-bootstrap'; +import { + Button, OverlayTrigger, Tooltip, Tab, Tabs, ControlLabel +} from 'react-bootstrap'; import Sample from 'src/models/Sample'; import Component from 'src/models/Component'; -import { permitOn } from 'src/components/common/uis'; -import SampleComponent from 'src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js'; +import SampleComponent from 'src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent'; const SampleComponentsGroup = ({ - materialGroup, deleteMixtureComponent, onChange, sample, - headIndex, dropSample,dropMaterial, lockAmountColumn, lockAmountColumnSolids, switchAmount, sampleComponents, - showModalWithMaterial, activeTab, handleTabSelect - }) => { - const contents = []; - if (sampleComponents && sampleComponents.length > 0) { - sampleComponents = sampleComponents.map((component) => { - if (!(component instanceof Component)) { - return new Component(component) - } - return component; - }); - let index = headIndex; - sampleComponents.forEach((sampleComponent) => { - index += 1; - contents.push(( - deleteMixtureComponent(sc, materialGroup)} - index={index} - dropMaterial={dropMaterial} - dropSample={dropSample} - lockAmountColumn={lockAmountColumn} - lockAmountColumnSolids={lockAmountColumnSolids} - showModalWithMaterial={showModalWithMaterial} - activeTab={activeTab} - handleTabSelect={handleTabSelect} - /> - )); - }); - } - - const headers = { - name: 'Label', - amount: 'Amount', - mass: 'Mass', - volume: 'Volume', - startingConc: 'Starting conc.', - concn: 'Conc.', - eq: 'Ratio', - ref: 'Ref', - purity: 'Purity', - density: 'Density', - }; + materialGroup, deleteMixtureComponent, onChange, sample, + headIndex, dropSample, dropMaterial, lockAmountColumn, lockAmountColumnSolids, switchAmount, sampleComponents, + showModalWithMaterial, activeTab, handleTabSelect +}) => { + const contents = []; + if (sampleComponents && sampleComponents.length > 0) { + sampleComponents = sampleComponents.map((component) => { + if (!(component instanceof Component)) { + return new Component(component); + } + return component; + }); + let index = headIndex; + sampleComponents.forEach((sampleComponent) => { + index += 1; + contents.push(( + deleteMixtureComponent(sc, materialGroup)} + index={index} + dropMaterial={dropMaterial} + dropSample={dropSample} + lockAmountColumn={lockAmountColumn} + lockAmountColumnSolids={lockAmountColumnSolids} + showModalWithMaterial={showModalWithMaterial} + activeTab={activeTab} + handleTabSelect={handleTabSelect} + /> + )); + }); + } - if (materialGroup === 'solid') { - headers.group = 'Solids'; - } else { - headers.group = 'Liquids'; - } + const headers = { + name: 'Label', + amount: 'Amount', + mass: 'Mass', + volume: 'Volume', + startingConc: 'Starting conc.', + concn: 'Total Conc.', + eq: 'Ratio', + ref: 'Ref', + purity: 'Purity', + density: 'Density', + }; + + if (materialGroup === 'solid') { + headers.group = 'Solids'; + } else { + headers.group = 'Liquids'; + } + + const switchAmountTooltip = () => ( + + Lock/unlock amounts
(mass/volume/mol) +
+ ); - const switchAmountTooltip = () => ( - Lock/unlock amounts
(mass/volume/mol)
- ); - - const SwitchAmountButton = (lockAmountColumn, switchAmount, materialGroup) => { - return ( - - - - ); - }; - - return ( -
- - + const SwitchAmountButton = (lockAmountColumn, switchAmount, materialGroup) => ( + + + + ); + + return ( +
+
+ @@ -96,57 +97,80 @@ const SampleComponentsGroup = ({ - - - - + + + + - {materialGroup === 'solid' && } - {materialGroup === 'liquid' && } + {materialGroup === 'solid' && ( + + )} + {materialGroup === 'liquid' && ( + + )} - {materialGroup === 'liquid' && } + + + + + + )} {materialGroup === 'solid' && } - + - - - {contents.map(item => item)} - -
{headers.group} {headers.name} {headers.ref}{SwitchAmountButton(lockAmountColumnSolids, switchAmount, materialGroup)} {headers.mass}{SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} {headers.volume} + {SwitchAmountButton(lockAmountColumnSolids, switchAmount, materialGroup)} {headers.mass} + + {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} {headers.volume} + {headers.amount} - - - - + {materialGroup === 'liquid' && ( - + {headers.density}{headers.concn} + {headers.concn} + + Total Conc. will only be calculated when we have a Total volume + + )} + > + + + + + {headers.purity} {headers.eq}
-
- ); - }; - - SampleComponentsGroup.propTypes = { - materialGroup: PropTypes.string.isRequired, - headIndex: PropTypes.number.isRequired, - deleteMixtureComponent: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - sample: PropTypes.instanceOf(Sample).isRequired, - dropSample: PropTypes.func.isRequired, - dropMaterial: PropTypes.func.isRequired, - switchAmount: PropTypes.func.isRequired, - lockAmountColumn: PropTypes.bool, - lockAmountColumnSolids: PropTypes.bool, - }; + + + {contents.map((item) => item)} + + +
+ ); +}; - SampleComponentsGroup.defaultProps = { - lockAmountColumn: false, - lockAmountColumnSolids: false - }; - - - export default SampleComponentsGroup; +SampleComponentsGroup.propTypes = { + materialGroup: PropTypes.string.isRequired, + headIndex: PropTypes.number.isRequired, + deleteMixtureComponent: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + sample: PropTypes.instanceOf(Sample).isRequired, + dropSample: PropTypes.func.isRequired, + dropMaterial: PropTypes.func.isRequired, + switchAmount: PropTypes.func.isRequired, + lockAmountColumn: PropTypes.bool, + lockAmountColumnSolids: PropTypes.bool, +}; + +SampleComponentsGroup.defaultProps = { + lockAmountColumn: false, + lockAmountColumnSolids: false +}; + +export default SampleComponentsGroup; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js index 9f1d8b1b89..05e22359a2 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js @@ -441,7 +441,7 @@ export default class SampleForm extends React.Component { title = '', block = false, notApplicable = false, - showInfoTooltip = false + showInfoTooltipTotalVol = false ) { if (sample.contains_residues && unit === 'l') return false; const value = !isNaN(sample[field]) ? sample[field] : null; @@ -498,7 +498,7 @@ export default class SampleForm extends React.Component { onChange={(e) => this.handleFieldChanged(field, e)} onMetricsChange={(e) => this.handleMetricsChange(e)} id={`numInput_${field}`} - showInfoTooltip={showInfoTooltip} + showInfoTooltipTotalVol={showInfoTooltipTotalVol} /> ); From 0a9e4776d24b16cd83df6f1016a0a49f563662a3 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Mon, 12 Aug 2024 15:33:16 +0200 Subject: [PATCH 10/12] feat: change component table for liquid components --- .../samples/propertiesTab/SampleComponent.js | 149 ++++---- .../propertiesTab/SampleComponentsGroup.js | 119 ++++--- .../propertiesTab/SampleDetailsComponents.js | 11 +- .../SampleDetailsComponentsDnd.js | 8 +- .../samples/propertiesTab/SampleForm.js | 323 ++++++++++-------- 5 files changed, 335 insertions(+), 275 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index 251f8d80d0..f4888613ce 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -15,6 +15,7 @@ import NumeralInputWithUnitsCompo from 'src/apps/mydb/elements/details/NumeralIn import Sample from 'src/models/Sample'; import { permitCls, permitOn } from 'src/components/common/uis'; import SvgWithPopover from 'src/components/common/SvgWithPopover'; +import SampleForm from "./SampleForm"; const matSource = { beginDrag(props) { @@ -172,7 +173,7 @@ class SampleComponent extends Component { return (
- {this.svgPreview(material, moleculeIupacName)} + {this.svgPreview(material, moleculeIupacName)}
); @@ -244,25 +245,29 @@ class SampleComponent extends Component { materialVolume(material) { if (material.contains_residues) { return notApplicableInput(); } + + const { + sample, lockAmountColumn, enableComponentLabel, enableComponentPurity + } = this.props; const metricPrefixes = ['m', 'n', 'u']; const metric = (material.metrics && material.metrics.length > 2 && metricPrefixes.indexOf(material.metrics[1]) > -1) ? material.metrics[1] : 'm'; return ( - -
- this.handleAmountChange(e, material.amount_l)} - onMetricsChange={this.handleMetricsChange} - bsStyle={material.amount_unit === 'l' ? 'success' : 'default'} - /> -
+ + this.handleAmountChange(e, material.amount_l)} + onMetricsChange={this.handleMetricsChange} + bsStyle={material.amount_unit === 'l' ? 'success' : 'default'} + /> ); } @@ -296,20 +301,28 @@ class SampleComponent extends Component { } componentMol(material, metricMol, metricPrefixesMol) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; + const { + sample, materialGroup, lockAmountColumn, lockAmountColumnSolids, enableComponentLabel, enableComponentPurity + } = this.props; + const lockColumn = materialGroup === 'liquid' ? lockAmountColumn : lockAmountColumnSolids; + return ( - this.handleAmountChange(e, material.amount_mol)} - onMetricsChange={this.handleMetricsChange} - bsStyle={material.amount_unit === 'mol' ? 'success' : 'default'} - /> + + this.handleAmountChange(e, material.amount_mol)} + onMetricsChange={this.handleMetricsChange} + bsStyle={material.amount_unit === 'mol' ? 'success' : 'default'} + /> + ); } @@ -389,7 +402,8 @@ class SampleComponent extends Component { mixtureComponent(props, style) { const { - sample, material, deleteMaterial, connectDragSource, connectDropTarget, activeTab + sample, material, deleteMaterial, connectDragSource, connectDropTarget, activeTab, + enableComponentLabel, enableComponentPurity } = props; const metricPrefixes = ['m', 'n', 'u']; const metricPrefixesMol = ['m', 'n']; @@ -410,51 +424,58 @@ class SampleComponent extends Component { {this.materialNameWithIupac(material)} - - {this.nameInput(material)} + + - {this.materialRef(material)} - - {this.materialVolume(material)} - - - {this.componentMol(material, metricMol, metricPrefixesMol)} - + {this.componentStartingConc(material, metricMolConc, metricPrefixesMolConc)} - {activeTab === 'concentration' && this.componentStartingConc(material, metricMolConc, metricPrefixesMolConc)} - {activeTab === 'density' && this.componentDensity(material)} + {this.componentDensity(material)} - {this.componentConc(material, metricMolConc, metricPrefixesMolConc)} + {this.materialVolume(material)} - - this.handlePurityChange(e, material.purity)} - /> - + {this.componentMol(material, metricMol, metricPrefixesMol)} this.handleRatioChange(e, material.equivalent)} /> - - - + {this.materialRef(material)} + + {this.componentConc(material, metricMolConc, metricPrefixesMolConc)} + + { + enableComponentLabel && ( + + {this.nameInput(material)} + + ) + } + + { + enableComponentPurity && ( + + this.handlePurityChange(e, material.purity)} + /> + + ) + } ); } @@ -484,10 +505,6 @@ class SampleComponent extends Component { {this.materialNameWithIupac(material)} - - {this.nameInput(material)} - - {this.materialRef(material)} @@ -599,4 +616,6 @@ SampleComponent.propTypes = { isOver: PropTypes.bool, lockAmountColumn: PropTypes.bool.isRequired, lockAmountColumnSolids: PropTypes.bool.isRequired, + enableComponentLabel: PropTypes.bool.isRequired, + enableComponentPurity: PropTypes.bool.isRequired, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js index 01a7e898f5..a7f674849e 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js @@ -10,7 +10,7 @@ import SampleComponent from 'src/apps/mydb/elements/details/samples/propertiesTa const SampleComponentsGroup = ({ materialGroup, deleteMixtureComponent, onChange, sample, headIndex, dropSample, dropMaterial, lockAmountColumn, lockAmountColumnSolids, switchAmount, sampleComponents, - showModalWithMaterial, activeTab, handleTabSelect + showModalWithMaterial, activeTab, handleTabSelect, enableComponentLabel, enableComponentPurity }) => { const contents = []; if (sampleComponents && sampleComponents.length > 0) { @@ -39,6 +39,8 @@ const SampleComponentsGroup = ({ showModalWithMaterial={showModalWithMaterial} activeTab={activeTab} handleTabSelect={handleTabSelect} + enableComponentLabel={enableComponentLabel} + enableComponentPurity={enableComponentPurity} /> )); }); @@ -49,7 +51,7 @@ const SampleComponentsGroup = ({ amount: 'Amount', mass: 'Mass', volume: 'Volume', - startingConc: 'Starting conc.', + startingConc: 'Stock', concn: 'Total Conc.', eq: 'Ratio', ref: 'Ref', @@ -88,64 +90,67 @@ const SampleComponentsGroup = ({ - - - - - - - - + + + + + + + + {enableComponentLabel && } + {enableComponentPurity && } - - - {headers.group} - {headers.name} - {headers.ref} - {materialGroup === 'solid' && ( - - {SwitchAmountButton(lockAmountColumnSolids, switchAmount, materialGroup)} {headers.mass} - - )} - {materialGroup === 'liquid' && ( - - {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} {headers.volume} - - )} - {headers.amount} - {materialGroup === 'liquid' && ( - - - - - - - - )} - {materialGroup === 'solid' && {headers.density}} + + + {headers.group} + + {materialGroup === 'solid' && ( + + {SwitchAmountButton(lockAmountColumnSolids, switchAmount, materialGroup)} {headers.mass} + + )} + + {materialGroup === 'liquid' && ( + + {headers.startingConc} + + )} + {materialGroup === 'liquid' && ( - {headers.concn} - - Total Conc. will only be calculated when we have a Total volume - - )} - > - - - - + {headers.density} - {headers.purity} - {headers.eq} - + )} + + {materialGroup === 'liquid' && ( + + {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} {headers.volume} + + )} + {headers.amount} + + {materialGroup === 'solid' && {headers.density}} + {headers.eq} + {headers.ref} + + {headers.concn} + + Total Conc. will only be calculated when we have a Total volume + + )} + > + + + + + + {enableComponentLabel && {headers.name}} + {enableComponentPurity && {headers.purity}} + {contents.map((item) => item)} @@ -166,11 +171,13 @@ SampleComponentsGroup.propTypes = { switchAmount: PropTypes.func.isRequired, lockAmountColumn: PropTypes.bool, lockAmountColumnSolids: PropTypes.bool, + enableComponentLabel: PropTypes.bool.isRequired, + enableComponentPurity: PropTypes.bool.isRequired, }; SampleComponentsGroup.defaultProps = { lockAmountColumn: false, - lockAmountColumnSolids: false + lockAmountColumnSolids: false, }; export default SampleComponentsGroup; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js index 15946a4cbd..4fddfb25f7 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js @@ -318,7 +318,7 @@ export default class SampleDetailsComponents extends React.Component { render() { const { - sample, isOver, canDrop + sample, isOver, canDrop, enableComponentLabel, enableComponentPurity } = this.props; const style = { padding: '2px 5px', @@ -360,6 +360,8 @@ export default class SampleDetailsComponents extends React.Component { showModalWithMaterial={this.showModalWithMaterial} handleTabSelect={this.handleTabSelect} activeTab={this.state.activeTab} + enableComponentLabel={enableComponentLabel} + enableComponentPurity={enableComponentPurity} /> @@ -389,9 +391,6 @@ SampleDetailsComponents.propTypes = { onChange: PropTypes.func.isRequired, isOver: PropTypes.bool.isRequired, canDrop: PropTypes.bool.isRequired, -}; - -SampleDetailsComponents.defaultProps = { - canDrop: true, - isOver: false + enableComponentLabel: PropTypes.bool.isRequired, + enableComponentPurity: PropTypes.bool.isRequired, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponentsDnd.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponentsDnd.js index 553b39f7ce..050b4e933c 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponentsDnd.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponentsDnd.js @@ -68,7 +68,9 @@ class SampleDetailsComponentsDnd extends React.Component { materialGroup, showModalWithMaterial, activeTab, - handleTabSelect + handleTabSelect, + enableComponentLabel, + enableComponentPurity, } = this.props; const style = { padding: '0px 0px', @@ -98,6 +100,8 @@ class SampleDetailsComponentsDnd extends React.Component { showModalWithMaterial={showModalWithMaterial} activeTab={activeTab} handleTabSelect={handleTabSelect} + enableComponentLabel={enableComponentLabel} + enableComponentPurity={enableComponentPurity} />
); @@ -121,4 +125,6 @@ SampleDetailsComponentsDnd.propTypes = { isOver: PropTypes.bool.isRequired, canDrop: PropTypes.bool.isRequired, connectDropTarget: PropTypes.func.isRequired, + enableComponentLabel: PropTypes.bool.isRequired, + enableComponentPurity: PropTypes.bool.isRequired, }; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js index 05e22359a2..5f2adab251 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js @@ -25,6 +25,8 @@ export default class SampleForm extends React.Component { isMolNameLoading: false, moleculeFormulaWas: props.sample.molecule_formula, selectedSampleType: props.sample.sample_type ? props.sample.sample_type : SampleTypesOptions[0], + enableComponentLabel: false, + enableComponentPurity: false, }; this.handleFieldChanged = this.handleFieldChanged.bind(this); @@ -45,6 +47,27 @@ export default class SampleForm extends React.Component { this.setState({ isMolNameLoading: false }); } + handleToggle = (key) => { + this.setState((prevState) => ({ + [key]: !prevState[key], + })); + }; + + renderCheckbox = (key, label, className) => { + const isChecked = this.state[key]; + + return ( + this.handleToggle(key)} + > + {label} + + ); + }; + formulaChanged() { return this.props.sample.molecule_formula !== this.state.moleculeFormulaWas; } @@ -61,7 +84,7 @@ export default class SampleForm extends React.Component { handleSampleTypeChanged(sampleType) { const { sample } = this.props; sample.updateSampleType(sampleType.value); - this.setState({ selectedSampleType: sampleType.value }) + this.setState({ selectedSampleType: sampleType.value }); this.props.parent.setState({ sample }); } @@ -548,28 +571,23 @@ export default class SampleForm extends React.Component { } totalAmount(sample) { - const content = []; const isDisabled = !sample.can_update; - if (sample.isMethodDisabled('amount_value') === false) { - if (!sample.contains_residues) { - content.push(this.numInput( - sample, - 'amount_l', - 'l', - ['m', 'u', 'n'], - 5, - 'Total volume', - 'l', - isDisabled, - '', - false, - false, - true - )); - } - - return content; + if (!sample.isMethodDisabled('amount_value') && !sample.contains_residues) { + return this.numInput( + sample, + 'amount_l', + 'l', + ['m', 'u', 'n'], + 5, + 'Total volume', + 'l', + isDisabled, + '', + false, + false, + true + ); } return ( @@ -759,17 +777,20 @@ export default class SampleForm extends React.Component { options={SampleTypesOptions} /> - ) + ); } mixtureComponentsList(sample) { + const { enableComponentLabel, enableComponentPurity } = this.state; + return ( - Mixture Components: - + ); } @@ -779,9 +800,8 @@ export default class SampleForm extends React.Component { const isPolymer = (sample.molfile || '').indexOf(' R# ') !== -1; const isDisabled = !sample.can_update; const polyDisabled = isPolymer || isDisabled; - const molarityBlocked = isDisabled ? true : this.state.molarityBlocked; - const densityBlocked = isDisabled ? true : !molarityBlocked; const { enableSampleDecoupled } = this.props; + const { selectedSampleType } = this.state; const minPadding = { padding: '4px 4px 4px 4px' }; if (sample.belongTo !== undefined && sample.belongTo !== null) { @@ -793,135 +813,144 @@ export default class SampleForm extends React.Component { - {this.sampleTypeInput()} + {this.sampleTypeInput()}
Basic Properties:
- {this.state.selectedSampleType !== 'Mixture' ? ( -
-
- - - - + + + + - - {sample.decoupled - && ( - - { - this.numInput(sample, 'molecular_mass', 'g/mol', ['n'], 5, 'Molecular mass', '', isDisabled) - } - - - )} - - - + -
- {this.textInput(sample, 'xref_inventory_label', 'Inventory label')} + ) : ( +
+
+ {this.textInput(sample, 'name', 'Name')} +
+
+ {this.textInput(sample, 'external_label', 'External label')} +
+
+ {this.textInput(sample, 'xref_inventory_label', 'Inventory label')} +
-
- )} + )} - {this.state.selectedSampleType === 'Mixture' ? this.totalAmount(sample) : null} + Mixture Components: + + + + + {selectedSampleType === 'Mixture' ? this.mixtureComponentsList(sample) : null} + +
+ {this.renderCheckbox('enableComponentLabel', 'Enable Label', 'enable-component-label')} + {this.renderCheckbox('enableComponentPurity', 'Enable Purity', 'enable-component-purity')} +
- {this.state.selectedSampleType === 'Mixture' ? this.mixtureComponentsList(sample) : null} @@ -349,7 +342,8 @@ class SampleComponent extends Component { } componentStartingConc(material, metricMolConc, metricPrefixesMolConc) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; + const { sample, lockAmountColumn } = this.props; + return ( @@ -383,7 +377,11 @@ class SampleComponent extends Component { } componentDensity(material) { - const lockColumn = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; + const { + sample, materialGroup, lockAmountColumn, lockAmountColumnSolids + } = this.props; + const lockColumn = materialGroup === 'liquid' ? lockAmountColumn : lockAmountColumnSolids; + return ( @@ -435,9 +433,8 @@ class SampleComponent extends Component { - {this.componentStartingConc(material, metricMolConc, metricPrefixesMolConc)} - - {this.componentDensity(material)} + {activeTab === 'concentration' && this.componentStartingConc(material, metricMolConc, metricPrefixesMolConc)} + {activeTab === 'density' && this.componentDensity(material)} {this.materialVolume(material)} @@ -606,6 +603,7 @@ export default compose( )(SampleComponent); SampleComponent.propTypes = { + sample: PropTypes.object.isRequired, material: PropTypes.instanceOf(Sample).isRequired, materialGroup: PropTypes.string.isRequired, deleteMaterial: PropTypes.func.isRequired, diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js index a7f674849e..104eff2d51 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js @@ -67,13 +67,19 @@ const SampleComponentsGroup = ({ const switchAmountTooltip = () => ( - Lock/unlock amounts
(mass/volume/mol) + + Lock/unlock amounts + + + (mass/stock/density) +
); const SwitchAmountButton = (lockAmountColumn, switchAmount, materialGroup) => ( - - - - - - - + + + + + + {enableComponentLabel && } {enableComponentPurity && } @@ -114,18 +119,22 @@ const SampleComponentsGroup = ({ {materialGroup === 'liquid' && ( - )} - {materialGroup === 'liquid' && ( - )} {materialGroup === 'liquid' && ( )} @@ -169,15 +178,10 @@ SampleComponentsGroup.propTypes = { dropSample: PropTypes.func.isRequired, dropMaterial: PropTypes.func.isRequired, switchAmount: PropTypes.func.isRequired, - lockAmountColumn: PropTypes.bool, - lockAmountColumnSolids: PropTypes.bool, + lockAmountColumn: PropTypes.bool.isRequired, + lockAmountColumnSolids: PropTypes.bool.isRequired, enableComponentLabel: PropTypes.bool.isRequired, enableComponentPurity: PropTypes.bool.isRequired, }; -SampleComponentsGroup.defaultProps = { - lockAmountColumn: false, - lockAmountColumnSolids: false, -}; - export default SampleComponentsGroup; diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js index 4fddfb25f7..ebf4a88b44 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleDetailsComponents.js @@ -20,7 +20,7 @@ export default class SampleDetailsComponents extends React.Component { showModal: false, droppedMaterial: null, activeTab: 'concentration', - lockAmountColumn: false, + lockAmountColumn: true, lockAmountColumnSolids: false, }; @@ -102,34 +102,37 @@ export default class SampleDetailsComponents extends React.Component { updatedSampleForAmountUnitChange(changeEvent) { const { sample } = this.props; - const {sampleID, amount, concType, updateVolume} = changeEvent; - const componentIndex = this.props.sample.components.findIndex( + const { sampleID, amount, concType, lockColumn } = changeEvent; + const componentIndex = sample.components.findIndex( (component) => component.id === sampleID ); const totalVolume = sample.amount_l; if (amount.unit === 'g' || amount.unit === 'l') { - sample.components[componentIndex].setAmount(amount, totalVolume); + sample.components[componentIndex].handleVolumeChange(amount, totalVolume); // volume given, update amount } else if (amount.unit === 'mol') { - sample.components[componentIndex].setMol(amount, totalVolume); + sample.components[componentIndex].handleAmountChange(amount, totalVolume); // amount given, update volume } else if (amount.unit === 'mol/l') { - sample.components[componentIndex].setConc(amount, totalVolume, concType, updateVolume); + sample.components[componentIndex].setConc(amount, totalVolume, concType, lockColumn); // starting conc. given, } + // update components ratio sample.updateMixtureComponentEquivalent(); } updateDensity(changeEvent) { const { sample } = this.props; - const { sampleID, amount, updateVolume } = changeEvent; - const componentIndex = this.props.sample.components.findIndex( + const { sampleID, amount, lockColumn } = changeEvent; + const componentIndex = sample.components.findIndex( (component) => component.id === sampleID ); const totalVolume = sample.amount_l; - sample.components[componentIndex].setDensity(amount, updateVolume, totalVolume); + sample.components[componentIndex].handleDensityChange(amount, lockColumn, totalVolume); + // sample.components[componentIndex].setDensity(amount, lockColumn, totalVolume); + // update components ratio sample.updateMixtureComponentEquivalent(); } diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js index 5f2adab251..94e35a54ac 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleForm.js @@ -933,24 +933,32 @@ export default class SampleForm extends React.Component { )} - - Mixture Components: - - - - - {selectedSampleType === 'Mixture' ? this.mixtureComponentsList(sample) : null} - -
- {this.renderCheckbox('enableComponentLabel', 'Enable Label', 'enable-component-label')} - {this.renderCheckbox('enableComponentPurity', 'Enable Purity', 'enable-component-purity')} -
- + {selectedSampleType === 'Mixture' && ( + <> + + Mixture Components: + + + + + + + {this.mixtureComponentsList(sample)} + + + + + + )} - + @@ -119,7 +119,8 @@ const SampleComponentsGroup = ({ {materialGroup === 'liquid' && ( )} diff --git a/app/packs/src/models/Component.js b/app/packs/src/models/Component.js index 99e5b7c3ed..277c672883 100644 --- a/app/packs/src/models/Component.js +++ b/app/packs/src/models/Component.js @@ -114,6 +114,17 @@ export default class Component extends Sample { } } + setMolarityDensity(amount, totalVolume) { + const purity = this.purity || 1.0; + this.molarity_value = this.concn = amount.value; + this.molarity_unit = amount.unit; + this.starting_molarity_value = 0; + + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = (this.molecule_molecular_weight * this.amount_mol) / purity; + this.amount_l = (this.amount_g / this.density) / 1000; + } + handleStockChange(amount, totalVolume, concType, lockColumn) { this.setConcentration(amount, concType, lockColumn);
-
-
- {this.moleculeInput()} - {this.stereoAbsInput()} - {this.stereoRelInput()} -
- {/*
- {this.topSecretCheckbox(sample)} -
*/} - { - enableSampleDecoupled ? ( -
- {this.decoupledCheckbox(sample)} -
- ) : null - } -
-
-
-
- {this.textInput(sample, 'name', 'Name')} -
-
- {this.textInput(sample, 'external_label', 'External label')} -
-
- {this.textInput(sample, 'xref_inventory_label', 'Inventory label')} + + {' '} + {selectedSampleType !== 'Mixture' ? ( +
+
+
+
+ {this.moleculeInput()} + {this.stereoAbsInput()} + {this.stereoRelInput()} +
+ { + enableSampleDecoupled ? ( +
+ {this.decoupledCheckbox(sample)} +
+ ) : null + }
-
- {this.drySolventCheckbox(sample)} +
+
+
+ {this.textInput(sample, 'name', 'Name')} +
+
+ {this.textInput(sample, 'external_label', 'External label')} +
+
+ {this.textInput(sample, 'xref_inventory_label', 'Inventory label')} +
+
+ {this.drySolventCheckbox(sample)} +
- {/*
- - {this.sampleSolvent(sample)} -
*/} - -
- { - this.textInput(sample, 'sum_formula', 'Sum formula') - } -
- - - - - {this.sampleAmount(sample)} - + + {sample.decoupled + && ( + + { + this.numInput(sample, 'molecular_mass', 'g/mol', ['n'], 5, 'Molecular mass', '', isDisabled) + } + + + )} + + + - - - ) : ( -
-
- {this.textInput(sample, 'name', 'Name')} -
-
- {this.textInput(sample, 'external_label', 'External label')} + + {this.sampleAmount(sample)} +
+ + +
-
- {/* eslint-disable-next-line jsx-a11y/label-has-for */} -
-
-
- - - { - this.numInputWithoutTable(sample, 'density', 'g/ml', ['n'], 5, '', '', polyDisabled, '', false, isPolymer) - } - - - { - this.numInputWithoutTable(sample, 'molarity_value', 'M', ['n'], 5, '', '', polyDisabled, '', false, isPolymer) - } - - -
- { - this.numInputWithoutTable(sample, 'purity', 'n', ['n'], 5, 'Purity/Concentration', '', isDisabled) +
+ { + this.textInput(sample, 'sum_formula', 'Sum formula') } +
+ + + + - - -
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-for */} +
- -
-
+
+ + + { + this.numInputWithoutTable(sample, 'density', 'g/ml', ['n'], 5, '', '', polyDisabled, '', false, isPolymer) + } + + + { + this.numInputWithoutTable(sample, 'molarity_value', 'M', ['n'], 5, '', '', polyDisabled, '', false, isPolymer) + } + + +
+ { + this.numInputWithoutTable(sample, 'purity', 'n', ['n'], 5, 'Purity/Concentration', '', isDisabled) + } +
+
+
+
+ {selectedSampleType === 'Mixture' ? this.totalAmount(sample) : null} +
Date: Wed, 14 Aug 2024 16:18:43 +0200 Subject: [PATCH 11/12] feat: change component table for liquid components when volume given, calculate the amount when amount is given, calculate the volume handle the calculation when the starting conc. or density being locked recalculate the entire row when starting conc. or density is updated except total concentration, the reference, and the ratio test: add test codes for Component model --- .../samples/propertiesTab/SampleComponent.js | 54 ++- .../propertiesTab/SampleComponentsGroup.js | 48 +-- .../propertiesTab/SampleDetailsComponents.js | 21 +- .../samples/propertiesTab/SampleForm.js | 44 ++- app/packs/src/models/Component.js | 206 ++++++----- app/packs/src/models/_Component.js | 346 ++++++++++++++++++ .../packs/src/models/Component.spec.js | 168 +++++++++ 7 files changed, 713 insertions(+), 174 deletions(-) create mode 100644 app/packs/src/models/_Component.js create mode 100644 spec/javascripts/packs/src/models/Component.spec.js diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js index f4888613ce..29f3d0c569 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponent.js @@ -87,16 +87,16 @@ class SampleComponent extends Component { handleAmountChange(e, value, concType, lockColumn) { if (e.value === value) return; + const { materialGroup } = this.props; if (this.props.onChange && e) { const event = { amount: e, type: 'amountChanged', - materialGroup: this.props.materialGroup, + materialGroup, sampleID: this.componentId(), concType, - updateVolume: !lockColumn, - + lockColumn, }; this.props.onChange(event); } @@ -111,8 +111,7 @@ class SampleComponent extends Component { type: 'densityChanged', materialGroup: this.props.materialGroup, sampleID: this.componentId(), - updateVolume: lockColumn, - + lockColumn, }; this.props.onChange(event); } @@ -209,16 +208,14 @@ class SampleComponent extends Component { handleRatioChange(e, value) { if (e.value === value) return; - const adjustAmount = this.props.materialGroup === 'liquid' ? this.props.lockAmountColumn : this.props.lockAmountColumnSolids; + const { materialGroup } = this.props; if (this.props.onChange && e) { const event = { newRatio: e.value, type: 'ratioChanged', sampleID: this.componentId(), - materialGroup: this.props.materialGroup, - adjustAmount, - + materialGroup, }; this.props.onChange(event); } @@ -247,7 +244,7 @@ class SampleComponent extends Component { if (material.contains_residues) { return notApplicableInput(); } const { - sample, lockAmountColumn, enableComponentLabel, enableComponentPurity + sample, enableComponentLabel, enableComponentPurity } = this.props; const metricPrefixes = ['m', 'n', 'u']; const metric = (material.metrics && material.metrics.length > 2 && metricPrefixes.indexOf(material.metrics[1]) > -1) ? material.metrics[1] : 'm'; @@ -263,7 +260,7 @@ class SampleComponent extends Component { metricPrefix={metric} metricPrefixes={metricPrefixes} precision={3} - disabled={!permitOn(sample) || lockAmountColumn} + disabled={!permitOn(sample)} onChange={(e) => this.handleAmountChange(e, material.amount_l)} onMetricsChange={this.handleMetricsChange} bsStyle={material.amount_unit === 'l' ? 'success' : 'default'} @@ -302,9 +299,8 @@ class SampleComponent extends Component { componentMol(material, metricMol, metricPrefixesMol) { const { - sample, materialGroup, lockAmountColumn, lockAmountColumnSolids, enableComponentLabel, enableComponentPurity + sample, enableComponentLabel, enableComponentPurity } = this.props; - const lockColumn = materialGroup === 'liquid' ? lockAmountColumn : lockAmountColumnSolids; return ( this.handleAmountChange(e, material.amount_mol)} onMetricsChange={this.handleMetricsChange} bsStyle={material.amount_unit === 'mol' ? 'success' : 'default'} @@ -327,10 +323,7 @@ class SampleComponent extends Component { } componentConc(material, metricMolConc, metricPrefixesMolConc) { - const { - materialGroup, lockAmountColumn, lockAmountColumnSolids, sample - } = this.props; - const lockColumn = materialGroup === 'liquid' ? lockAmountColumn : lockAmountColumnSolids; + const { sample } = this.props; return ( this.handleAmountChange(e, material.concn, 'targetConc', lockColumn)} + disabled={!permitOn(sample)} + onChange={(e) => this.handleAmountChange(e, material.concn, 'targetConc')} onMetricsChange={this.handleMetricsChange} /> this.handleAmountChange(e, material.startingConc, 'startingConc', lockColumn)} + disabled={!permitOn(sample) || lockAmountColumn} + onChange={(e) => this.handleAmountChange(e, material.startingConc, 'startingConc', lockAmountColumn)} onMetricsChange={this.handleMetricsChange} /> this.handleDensityChange(e, material.density, lockColumn)} />
- {headers.startingConc} - - {headers.density} +
+ + + + + {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} +
- {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} {headers.volume} + {headers.volume} {headers.amount}
- {selectedSampleType === 'Mixture' ? this.totalAmount(sample) : null} -
+ {this.totalAmount(sample)} +
+
+ {this.renderCheckbox('enableComponentLabel', 'Enable Label', 'enable-component-label')} + {this.renderCheckbox('enableComponentPurity', 'Enable Purity', 'enable-component-purity')} +
+
0 && this.material_group !== 'solid') { - this.setAmountDensity(amount, totalVolume); - } else { - this.setAmountConc(amount, totalVolume); - } - } - - // refactor this part - setMol(amount, totalVolume) { - if (Number.isNaN(amount.value) || amount.unit !== 'mol') return; + // Volume related codes - this.amount_mol = amount.value; - const purity = this.purity || 1.0; + handleVolumeChange(amount, totalVolume) { + if (!amount.unit || Number.isNaN(amount.value)) return; - if (this.density && this.density > 0 && this.material_group === 'liquid') { - // update density - this.starting_molarity_value = 0; + this.setVolume(amount); - this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / this.purity; + const purity = this.purity || 1.0; - if (totalVolume && totalVolume > 0) { - const concentration = this.amount_mol / (totalVolume * purity); - this.molarity_value = concentration; - this.concn = concentration; + if (this.material_group === 'liquid') { + if (this.density && this.density > 0) { + this.calculateAmountFromDensity(totalVolume, purity); + } else if (this.starting_molarity_value && this.starting_molarity_value > 0) { + this.calculateAmountFromConcentration(totalVolume, purity); } - } else if (this.material_group === 'solid') { - // update concentrations - if (this.amount_l === 0 || this.amount_g === 0) { - this.molarity_value = 0; - this.concn = 0; - } else if (totalVolume && totalVolume > 0) { - const concentration = this.amount_mol / (totalVolume * purity); - - this.concn = concentration; - this.molarity_value = concentration; + } - this.molarity_unit = 'M'; - } + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; } } - setAmountDensity(amount, totalVolume) { - this.amount_l = amount.value; - this.starting_molarity_value = 0; - const purity = this.purity || 1.0; + setVolume(amount) { if (this.material_group === 'liquid') { - this.amount_g = (this.amount_l * 1000) * this.density; - this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.amount_l = amount.value; + } else if (this.material_group === 'solid') { + this.amount_g = amount.value; } } - setAmountConc(amount, totalVolume) { + // Volume related codes ends + + // Amount related codes + + handleAmountChange(amount, totalVolume) { + if (Number.isNaN(amount.value) || amount.unit !== 'mol') return; + + this.amount_mol = amount.value; + const purity = this.purity || 1.0; - if (amount.unit === 'l') { - this.amount_l = amount.value; - if (this.starting_molarity_value) { - this.amount_mol = this.starting_molarity_value * this.amount_l * purity; - } - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } else if (amount.unit === 'g') { - this.amount_g = amount.value; - this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; - if (totalVolume) { - this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); - this.molarity_unit = 'M'; - } - if (this.amount_l === 0 || this.amount_g === 0) { - this.molarity_value = this.concn = 0; + if (this.material_group === 'liquid') { + if (this.density && this.density > 0) { // if density is given + this.calculateVolumeFromDensity(purity); + } else if (this.starting_molarity_value && this.starting_molarity_value > 0) { // if stock concentration is given + this.calculateVolumeFromConcentration(purity); } } - this.density = 0; + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; + this.molarity_unit = 'M'; + } } - setConc(amount, totalVolume, concType, updateVolume) { - if (!amount.unit || isNaN(amount.value) || amount.unit !== 'mol/l') { return; } + // Amount related codes ends + + // Stock related codes + + setConc(amount, totalVolume, concType, lockColumn) { + if (!amount.unit || Number.isNaN(amount.value) || amount.unit !== 'mol/l') { return; } if (this.density && this.density > 0 && concType !== 'startingConc' && this.material_group !== 'solid') { this.setMolarityDensity(amount, totalVolume); } else { - this.setMolarity(amount, totalVolume, concType, updateVolume); + this.handleStockChange(amount, totalVolume, concType, lockColumn); } } - setMolarityDensity(amount, totalVolume) { + handleStockChange(amount, totalVolume, concType, lockColumn) { + this.setConcentration(amount, concType, lockColumn); + const purity = this.purity || 1.0; - this.molarity_value = this.concn = amount.value; - this.molarity_unit = amount.unit; - this.starting_molarity_value = 0; - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = (this.molecule_molecular_weight * this.amount_mol) / purity; - this.amount_l = (this.amount_g / this.density) / 1000; + if (this.amount_l && this.amount_l > 0) { + this.calculateAmountFromConcentration(totalVolume, purity); + } else if (this.amount_mol && this.amount_mol > 0) { + this.calculateVolumeFromConcentration(purity); + } } - setMolarity(amount, totalVolume, concType, updateVolume) { - const purity = this.purity || 1.0; + setConcentration(amount, concType, lockColumn) { if (concType !== 'startingConc') { this.concn = amount.value; this.molarity_value = amount.value; this.molarity_unit = amount.unit; - } else { + } else if (!lockColumn) { this.starting_molarity_value = amount.value; this.starting_molarity_unit = amount.unit; } - if (this.material_group === 'liquid' && updateVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); - } else if (this.material_group === 'liquid' && !updateVolume) { - this.amount_mol = this.starting_molarity_value * this.amount_l * purity; - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); - } else if (this.material_group === 'solid' && this.concn && totalVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = this.molecule_molecular_weight * (this.amount_mol / purity); - } else if (this.concn === 0) { - this.amount_g = 0; - this.amount_l = 0; - } this.density = 0; } - setDensity(density, updateVolume, totalVolume) { + // Stock related codes ends + + // Density related codes + + handleDensityChange(amount, lockColumn, totalVolume) { + if (!amount.unit || Number.isNaN(amount.value) || amount.unit !== 'g/ml') return; + const purity = this.purity || 1.0; - if (!density.unit || isNaN(density.value) || density.unit !== 'g/ml') { return; } - this.density = density.value; + this.setDensity(amount, lockColumn); + + if (this.amount_l && this.amount_l > 0) { + this.calculateAmountFromDensity(totalVolume, purity); + } else if (this.amount_mol && this.amount_mol > 0) { + this.calculateVolumeFromDensity(purity); + } + } + + setDensity(amount, lockColumn) { + if (!lockColumn) { + this.density = amount.value; + this.starting_molarity_value = 0; + } + } + + // Density related codes ends + + // Case 1.1: Calculate Amount from Volume, Density, Molecular Weight, and Purity + calculateAmountFromDensity(totalVolume, purity) { this.starting_molarity_value = 0; - if (updateVolume) { - this.amount_mol = this.molarity_value * totalVolume * purity; - this.amount_g = this.amount_mol * this.molecule_molecular_weight / purity; - this.amount_l = (this.amount_g / this.density) / 1000; - } else { + if (this.material_group === 'liquid') { this.amount_g = (this.amount_l * 1000) * this.density; - this.amount_mol = this.amount_g * purity / this.molecule_molecular_weight; - this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; } } + // Case 1.2: Calculate Amount from Volume, Concentration, and Purity + calculateAmountFromConcentration(totalVolume, purity) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + + if (totalVolume) { + const concentration = this.amount_mol / (totalVolume * purity); + this.concn = concentration; + this.molarity_value = concentration; + this.molarity_unit = 'M'; + } + } + + // Case 2.1: Calculate Volume from Amount, Density, Molecular Weight, and Purity + calculateVolumeFromDensity(purity) { + this.starting_molarity_value = 0; + this.amount_l = (this.amount_mol * this.molecule_molecular_weight * purity) / (this.density * 1000); + } + + // Case 2.2: Calculate Volume from Amount, Concentration, and Purity + calculateVolumeFromConcentration(purity) { + this.density = 0; + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + } + updateRatio(newRatio, materialGroup, totalVolume, referenceMoles) { if (this.equivalent === newRatio) { return; } diff --git a/app/packs/src/models/_Component.js b/app/packs/src/models/_Component.js new file mode 100644 index 0000000000..de7390de7a --- /dev/null +++ b/app/packs/src/models/_Component.js @@ -0,0 +1,346 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable camelcase */ +import React from 'react'; +import Sample from 'src/models/Sample'; + +export default class Component extends Sample { + constructor(props) { + super(props); + } + + get has_density() { + return this.density > 0 && this.starting_molarity_value === 0; + } + + get amount_mol() { + return this._amount_mol; + } + + set amount_mol(amount_mol) { + this._amount_mol = amount_mol; + } + + get amount_g() { + return this._amount_g; + } + + set amount_g(amount_g) { + return this._amount_g = amount_g; + } + + get amount_l() { + return this._amount_l; + } + + set amount_l(amount_l) { + return this._amount_l = amount_l; + } + + get svgPath() { + return this.molecule && this.molecule.molecule_svg_file + ? `/images/molecules/${this.molecule.molecule_svg_file}` : ''; + } + + setAmount(amount, totalVolume) { + if (!amount.unit || Number.isNaN(amount.value)) { + return; + } + if (this.density && this.density > 0 && this.material_group !== 'solid') { + this.calculateAmountFromDensity(amount, totalVolume); + } else { + this.calculateAmountFromConcentration(amount, totalVolume); + } + } + + // refactor this part + setMol(amount, totalVolume) { + if (Number.isNaN(amount.value) || amount.unit !== 'mol') return; + + this.amount_mol = amount.value; + const purity = this.purity || 1.0; + + if (this.material_group === 'liquid') { + if (this.density && this.density > 0) { // if density is given + this.starting_molarity_value = 0; + this.amount_l = (this.amount_mol * this.molecule_molecular_weight * purity) / (this.density * 1000); + } else { // if stock concentration is given + this.density = 0; + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + } + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; + this.molarity_unit = 'M'; + } + } + if (this.material_group === 'solid') { + // update concentrations + if (this.amount_l === 0 || this.amount_g === 0) { + this.molarity_value = 0; + this.concn = 0; + } else if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + + this.concn = concentration; + this.molarity_value = concentration; + + this.molarity_unit = 'M'; + } + } + } + + calculateAmountFromDensity(amount, totalVolume) { + this.starting_molarity_value = 0; + const purity = this.purity || 1.0; + + if (this.material_group === 'liquid') { + this.amount_g = (this.amount_l * 1000) * this.density; + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; + + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; + } + } + } + + calculateAmountFromDensity(amount, totalVolume) { + this.amount_l = amount.value; + this.starting_molarity_value = 0; + const purity = this.purity || 1.0; + if (this.material_group === 'liquid') { + this.amount_g = (this.amount_l * 1000) * this.density; + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; + + const concentration = this.amount_mol / (totalVolume * purity); + this.molarity_value = concentration; + this.concn = concentration; + } + } + + setAmountConc(amount, totalVolume) { + const purity = this.purity || 1.0; + if (amount.unit === 'l') { + this.amount_l = amount.value; + if (this.starting_molarity_value) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + } + + const concentration = this.amount_mol / (totalVolume * purity); + this.concn = concentration; + this.molarity_value = concentration; + this.molarity_unit = 'M'; + } else if (amount.unit === 'g') { + this.amount_g = amount.value; + this.amount_mol = (this.amount_g * purity) / this.molecule_molecular_weight; + if (totalVolume) { + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } + + if (this.amount_l === 0 || this.amount_g === 0) { + this.molarity_value = this.concn = 0; + } + } + + this.density = 0; + } + + setConc(amount, totalVolume, concType, lockColumn) { + if (!amount.unit || Number.isNaN(amount.value) || amount.unit !== 'mol/l') { return; } + + if (this.density && this.density > 0 && concType !== 'startingConc' && this.material_group !== 'solid') { + this.setMolarityDensity(amount, totalVolume); + } else { + this.handleStockChange(amount, totalVolume, concType, lockColumn); + // this.setMolarity(amount, totalVolume, concType, updateVolume); + } + } + + handleVolumeChange(amount, totalVolume) { + if (!amount.unit || Number.isNaN(amount.value)) return; + + this.amount_l = amount.value; + + if (this.material_group === 'liquid') { + if (this.density && this.density > 0) { + this.calculateAmountFromDensity(amount, totalVolume); + } else if (this.starting_molarity_value && this.starting_molarity_value > 0) { + this.calculateAmountFromConcentration(amount, totalVolume); + } + } + } + + handleStockChange(amount, totalVolume, concType, lockColumn) { + const purity = this.purity || 1.0; + + this.setConcentration(amount, concType, lockColumn); + + if (this.amount_l && this.amount_l > 0) { + this.calculateAmountFromConcentration(purity, totalVolume); + } else if (this.amount_mol && this.amount_mol > 0) { + this.calculateVolumeFromConcentration(purity); + } + + if (totalVolume && totalVolume > 0) { + const concentration = this.amount_mol / (totalVolume * purity); + this.concn = concentration; + this.molarity_value = concentration; + } + } + + setConcentration(amount, concType, lockColumn) { + if (concType !== 'startingConc') { + this.concn = amount.value; + this.molarity_value = amount.value; + this.molarity_unit = amount.unit; + } else if (!lockColumn) { + this.starting_molarity_value = amount.value; + this.starting_molarity_unit = amount.unit; + } + } + + // Case 1.2: Calculate Amount from Volume, Concentration, and Purity + calculateAmountFromConcentration(purity, totalVolume) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + + if (totalVolume) { + const concentration = this.amount_mol / (totalVolume * purity); + this.concn = concentration; + this.molarity_value = concentration; + this.molarity_unit = 'M'; + } + } + + // Case 2.2: Calculate Volume from Amount, Concentration, and Purity + calculateVolumeFromConcentration(purity) { + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + } + + setMolarityDensity(amount, totalVolume) { + const purity = this.purity || 1.0; + this.molarity_value = this.concn = amount.value; + this.molarity_unit = amount.unit; + this.starting_molarity_value = 0; + + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = (this.molecule_molecular_weight * this.amount_mol) / purity; + this.amount_l = (this.amount_g / this.density) / 1000; + } + + setMolarity(amount, totalVolume, concType, lockColumn) { + const purity = this.purity || 1.0; + if (concType !== 'startingConc') { + this.concn = amount.value; + this.molarity_value = amount.value; + this.molarity_unit = amount.unit; + } else if (!lockColumn) { + this.starting_molarity_value = amount.value; + this.starting_molarity_unit = amount.unit; + } + if (this.material_group === 'liquid' && lockColumn) { + this.amount_mol = this.starting_molarity_value * this.amount_l * purity; + this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + } else if (this.material_group === 'liquid' && !lockColumn) { + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + } else if (this.material_group === 'solid' && this.concn && totalVolume) { + this.amount_mol = this.molarity_value * totalVolume * purity; + this.amount_g = this.molecule_molecular_weight * (this.amount_mol / purity); + } else if (this.concn === 0) { + this.amount_g = 0; + this.amount_l = 0; + } + + this.density = 0; + } + + setDensity(density, lockColumn, totalVolume) { + // const purity = this.purity || 1.0; + if (!density.unit || isNaN(density.value) || density.unit !== 'g/ml') { return; } + + this.density = density.value; + this.starting_molarity_value = 0; + + // const concentration = (this.amount_mol / (totalVolume * purity)) || 0; + + // this.concn = concentration; + // this.molarity_value = concentration; + + this.amount_g = 0; + this.amount_l = 0; + this.amount_mol = 0; + + // if (lockColumn) { + // this.amount_g = (this.amount_l * 1000) * this.density; + // this.amount_mol = this.amount_g * purity / this.molecule_molecular_weight; + // this.concn = this.molarity_value = this.amount_mol / (totalVolume * purity); + // } else { + // this.amount_mol = this.molarity_value * totalVolume * purity; + // this.amount_g = this.amount_mol * this.molecule_molecular_weight / purity; + // this.amount_l = (this.amount_g / this.density) / 1000; + // } + } + + updateRatio(newRatio, materialGroup, totalVolume, referenceMoles) { + if (this.equivalent === newRatio) { return; } + + const purity = this.purity || 1.0; + this.amount_mol = newRatio * referenceMoles; + this.equivalent = newRatio; + + if (materialGroup === 'liquid') { + if (!this.has_density) { + this.amount_l = this.amount_mol / (this.starting_molarity_value * purity); + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } else if (this.has_density) { + this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; + this.amount_l = this.amount_g / (this.density * 1000); + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + this.molarity_unit = 'M'; + } + } else if (materialGroup === 'solid') { + this.amount_g = (this.amount_mol * this.molecule_molecular_weight) / purity; + this.molarity_value = this.concn = this.amount_mol / (totalVolume * purity); + } + } + + setPurity(purity, totalVolume) { + if (!isNaN(purity) && purity >= 0 && purity <= 1) { + this.purity = purity; + this.amount_mol = this.molarity_value * totalVolume * this.purity; + } + } + + get svgPath() { + return this.molecule && this.molecule.molecule_svg_file + ? `/images/molecules/${this.molecule.molecule_svg_file}` : ''; + } + + serializeComponent() { + return { + id: this.id, + name: this.name, + position: this.position, + component_properties: { + amount_mol: this.amount_mol, + amount_l: this.amount_l, + amount_g: this.amount_g, + density: this.density, + molarity_unit: this.molarity_unit, + molarity_value: this.molarity_value, + starting_molarity_value: this.starting_molarity_value, + starting_molarity_unit: this.starting_molarity_unit, + molecule_id: this.molecule.id, + equivalent: this.equivalent, + parent_id: this.parent_id, + material_group: this.material_group, + reference: this.reference, + purity: this.purity, + } + }; + } +} diff --git a/spec/javascripts/packs/src/models/Component.spec.js b/spec/javascripts/packs/src/models/Component.spec.js new file mode 100644 index 0000000000..1d8b8cb6a3 --- /dev/null +++ b/spec/javascripts/packs/src/models/Component.spec.js @@ -0,0 +1,168 @@ +import expect from 'expect'; +import Component from '../../../../../app/packs/src/models/Component.js'; +import Molecule from '../../../../../app/packs/src/models/Molecule.js'; + +describe('Component', () => { + let component; + + beforeEach(() => { + component = new Component({}); + component.molecule = new Molecule(); + component.molecule.molecular_weight = 18.010564684; // Set a default molecular weight + component.purity = 1.0; // Set a default purity + }); + + describe('handleVolumeChange', () => { + it('should set volume and calculate amount from density when material group is liquid', () => { + const amount = { unit: 'l', value: 2 }; + component.material_group = 'liquid'; + component.density = 1.0; //1 g/mL + component.molecule.molecular_weight = 18.010564684; + + component.handleVolumeChange(amount, 200); + + expect(component.amount_l).to.equal(2); + expect(component.amount_g).to.equal(2000); // 2 liters * 1 g/mL = 2000 g + expect(component.amount_mol).to.be.closeTo(111.07, 0.01); // 2000g / 18.01g/mol + }); + + it('should set volume and calculate amount and molarity, when material group is liquid, and stock (starting' + + ' concentration) is given', () => { + const amount = { unit: 'l', value: 2 }; + component.material_group = 'liquid'; + component.starting_molarity_value = 0.1; // Molarity of 0.1 mmol/L + + component.handleVolumeChange(amount, 200); + + expect(component.amount_mol).to.equal(0.0002); // 0.1 mmol/L * 2 liters = 0.2 mmol = 0.0002 mol + expect(component.molarity_value).to.equal(0.000001); // 0.2 mmol / 200 liters = 0.001 mmol/L = 0.000001 mol/L + }); + }); + + describe('handleAmountChange', () => { + it('should set amount in mol and calculate volume if stock (starting concentration), when material group is' + + ' liquid', () => { + const amount = { unit: 'mol', value: 2 }; + component.material_group = 'liquid'; + component.starting_molarity_value = 1.0; // 1 Molar solution + component.purity = 1.0; + + component.handleAmountChange(amount, 2); + + expect(component.amount_mol).to.equal(2); + expect(component.amount_l).to.equal(2); // 2 mol / (1 mol/L * 1) = 2 L + }); + + it('should set amount in mol and calculate volume if density is given, when material group is liquid', () => { + const amount = { unit: 'mol', value: 2 }; + component.material_group = 'liquid'; + component.density = 1.0; //1 g/mL + component.purity = 1.0; // 100% purity + + component.handleAmountChange(amount, 2); + + // amount_l = (amount_mol * molecule_molecular_weight * purity) / (density * 1000) + // So, amount_l = (2 mol * 18.010564684 g/mol * 1) / (1000 g/L) + const expectedAmountL = (2 * 18.010564684) / 1000; // = 0.036021129368 L (approximately) + + expect(component.amount_mol).toBe(2); + expect(component.amount_l).toBeCloseTo(expectedAmountL, 6); // Check to a precision of 6 decimal places + }); + }); + + describe('handleDensityChange', () => { + it('should set density and calculate amount when volume is known, when material group is liquid', () => { + const density = { unit: 'g/ml', value: 2.0 }; + component.material_group = 'liquid'; + component.amount_l = 2.0; + + component.handleDensityChange(density, false, 2); + + // amount_g = (amount_l * 1000) * density; + // amount_mol = (amount_g * purity) / molecule_molecular_weight; + const expectedAmountG = component.amount_l * 1000 * density.value; // 2 L * 1000 g/L * 2.0 = 4000 g + const expectedAmountMol = expectedAmountG / component.molecule.molecular_weight; // 4000 g / 18.010564684 g/mol + + expect(component.density).toBe(2.0); + expect(component.amount_g).toBe(expectedAmountG); // 4000 g + expect(component.amount_mol).toBeCloseTo(expectedAmountMol, 0.01); // Approximately 222.1 mol + }); + + it('should set density and calculate volume when amount is known, when material group is liquid', () => { + component.amount_mol = 1.0; // 1 mole of the substance + component.molecule_molecular_weight = 18.010564684; // g/mol + const density = { unit: 'g/ml', value: 2.0 }; // g/mL (which is equivalent to 2000 g/L) + const purity = 1.0; // 100% purity + + component.handleDensityChange(density, false, 2); + + // amount_l = (amount_mol * molecule_molecular_weight * purity) / (density * 1000) + // amount_l = (1.0 mol * 18.010564684 g/mol * 1.0) / (2.0 g/mL * 1000) + const expectedAmountL = (component.amount_mol * component.molecule_molecular_weight * purity) / (component.density * 1000); + + expect(component.density).toBe(2.0); + expect(component.amount_l).to.be.closeTo(expectedAmountL, 0.01); // Allow a small margin for floating-point arithmetic + }); + }); + + describe('setPurity', () => { + it('should set purity and adjust amount_mol accordingly', () => { + component.molarity_value = 1.0; + component.setPurity(0.9, 2); // Setting purity to 0.9 + + expect(component.purity).to.equal(0.9); + expect(component.amount_mol).to.equal(1.8); // 1 mol/L * 2L * 0.9 purity = 1.8 mol + }); + + it('should not set purity if it is out of bounds', () => { + component.setPurity(1.1, 2); // Invalid purity (> 1) + expect(component.purity).to.not.equal(1.1); + + component.setPurity(-0.5, 2); // Invalid purity (< 0) + expect(component.purity).to.not.equal(-0.5); + }); + }); + + describe('serializeComponent', () => { + it('should return a serialized object of the component', () => { + component.id = 1; + component.name = 'Test Component'; + component.position = 1; + component.amount_mol = 2.0; + component.amount_g = 20.0; + component.amount_l = 1.5; + component.density = 1.2; + component.molarity_value = 0.5; + component.starting_molarity_value = 0.1; + component.equivalent = 1.0; + component.parent_id = 2; + component.material_group = 'solid'; + component.purity = 0.9; + component.molecule.id = 101; + + const serialized = component.serializeComponent(); + + expect(serialized).to.deep.equal({ + id: 1, + name: 'Test Component', + position: 1, + component_properties: { + amount_mol: 2.0, + amount_g: 20.0, + amount_l: 1.5, + density: 1.2, + molarity_unit: 'M', + molarity_value: 0.5, + starting_molarity_value: 0.1, + starting_molarity_unit: undefined, // Not set in this case + molecule_id: 101, + equivalent: 1.0, + parent_id: 2, + material_group: 'solid', + reference: undefined, // Not set in this case + purity: 0.9, + } + }); + }); + }); +}); From 9812ef11bea300e5180df121b109be0a9f7070f0 Mon Sep 17 00:00:00 2001 From: Tasnim Mehzabin Date: Tue, 27 Aug 2024 00:33:52 +0200 Subject: [PATCH 12/12] feat: change the position of the lock button fix: total conc. changes related fields --- .../samples/propertiesTab/SampleComponentsGroup.js | 6 +++--- app/packs/src/models/Component.js | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js index 104eff2d51..29c38c531d 100644 --- a/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js +++ b/app/packs/src/apps/mydb/elements/details/samples/propertiesTab/SampleComponentsGroup.js @@ -97,7 +97,7 @@ const SampleComponentsGroup = ({
-
+
+ {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)} - {SwitchAmountButton(lockAmountColumn, switchAmount, materialGroup)}