From c85856e3936ff06cd4ab72c747724bb3fcb2d378 Mon Sep 17 00:00:00 2001 From: Luciano Maiwald Date: Wed, 30 Oct 2024 13:18:49 +0100 Subject: [PATCH] refactor: Remove UNSAFE_ react lifecycle hooks (#2196) * Remove UNSAFE_componentWillReceiveProps from CollectionSubtree We don't need to copy the root prop into state. If we don't, there is no need to update the state. * Remove UNSAFE_componentWillReceiveProps from ElementsTableSampleEntries * Remove UNSAFE_componentWillReceiveProps from ToolbarTemplateCreator * Remove unnecessary UNSAFE_componentWillMount from ReactQuill * Remove UNSAFE_ react life cycle hooks from QuillEditor * Update deprecated componentWillReceiveProps in MetadataContainer * Remove UNSAFE_componentWillUpdate from SectionSpectrum The component already contains a `componentDidUpdate` method that also calls `this.updateThumbNails`. * Remove UNSAFE_componentWillReceiveProps in ContainerComponent * Remove UNSAFE_componentWillReceiveProps in SampleComputedProps * Remove UNSAFE_componentWillReceiveProps from EditableTable * Remove UNSAFE_componentWillReceiveProps from LineChartContainer Also update component to use createRef instead of ReactDOM.findDOMNode. * Remove UNSAFE_componentWillReceiveProps from EditableCell * Remove UNSAFE_componentWillReceiveProps from QuillViewer * Remove UNSAFE_componentWillReceiveProps from NmrSimTab * Remove UNSAFE_componentWillReceiveProps from ComputedPropsGraphContainer * Remove UNSAFE_componentWillReceiveProps from WellplateDetails * Remove UNSAFE_componentWillReceiveProps from WellplateDetailsContainers * Remove obsolete UNSAFE_componentWillReceiveProps from WellContainer The hideOverlay() does not exist and is not passed to the component anymore. * Remove UNSAFE_componentWillReceiveProps from LineChartWrapper * Remove UNSAFE_componentWillReceiveProps from SampleDetails * Remove UNSAFE_componentWillReceiveProps from SampleForm * Remove UNSAFE_componentWillReceiveProps from NumeralInput * Fix ResearchPlanMetadata form With this change updating array values has been fixed by correctly returning a new state in the `setState` handler. This change also removes researchPlan from state, because changes are only affected by its metadata. We also clean up state management by removing all `ref` for state updates in favour of proper `setState` calls using `handleFieldChange`. Lastly, this also updates the UNSAFE_componentWillReceiveProps life cycle hook to componentDidUpdate. * Remove UNSAFE_componentWillReceiveProps from NumeralInputWithUnitsCompo * Update state management in admin/textTemplates This patch separates state management from presentational components and gets rid of UNSAFE_componentWillReceiveProps. * Remove UNSAFE_componentWillReceiveProps from ScreenDetailsContainers * Remove UNSAFE_componentWillReceiveProps from EmbeddedResearchPlanDetails * Remove UNSAFE_componentWillReceiveProps from ScreenDetails * Remove UNSAFE_componentWillReceiveProps from ResearchPlanDetails * Remove temperature from state in ReactionDetailsMainProperties We don't ever modify the prop so we don't need to store it in state and thus don't need to keep props and state in sync. * Remove UNSAFE_componentWillReceiveProps from ReactionDetails * Remove reaction from state in ReactionDetailsScheme We were never updating the reaction state so we can simply use the prop value and don't have to worry about keeping state and props in sync. * Remove reaction from state in ReactionDetailsContainers This removes reaction from state so we don't have to handle keeping props and state in sync. We are however still just mutating the variable instead of creating new instances and passing those to our callbacks and handlers, but that is for another time. * Lazily calculate reaction duration display This way we don't have to worry about when we need to calculate the display values and can get rid of the life cycle methods that modified the reaction prop. --- .../apps/admin/textTemplates/TextTemplate.js | 303 ------------------ .../textTemplates/TextTemplateContainer.js | 143 ++++++--- .../admin/textTemplates/TextTemplateForm.js | 129 ++++++++ .../admin/textTemplates/TextTemplateIcon.js | 4 +- .../textTemplates/TextTemplateSelector.js | 128 ++++++++ .../mydb/collections/CollectionSubtree.js | 106 +++--- .../mydb/elements/details/NumeralInput.js | 13 +- .../details/NumeralInputWithUnitsCompo.js | 10 +- .../details/reactions/ReactionDetails.js | 15 +- .../ReactionDetailsMainProperties.js | 87 ++--- .../analysesTab/ReactionDetailsContainers.js | 34 +- .../ReactionDetailsProperties.js | 7 - .../schemeTab/ReactionDetailsDuration.js | 7 - .../schemeTab/ReactionDetailsScheme.js | 51 ++- .../details/reports/SectionSpectrum.js | 4 - .../researchPlans/ResearchPlanDetails.js | 13 +- .../researchPlans/ResearchPlanMetadata.js | 139 ++++---- .../elements/details/samples/SampleDetails.js | 6 +- .../samples/nmrSimTab/LineChartWrapper.js | 22 +- .../details/samples/nmrSimTab/NmrSimTab.js | 7 +- .../samples/propertiesTab/SampleForm.js | 8 +- .../elements/details/screens/ScreenDetails.js | 14 +- .../analysesTab/ScreenDetailsContainers.js | 11 +- .../EmbeddedResearchPlanDetails.js | 19 +- .../details/wellplates/WellplateDetails.js | 10 +- .../analysesTab/WellplateDetailsContainers.js | 12 +- .../wellplates/designerTab/WellContainer.js | 7 - .../list/ElementsTableSampleEntries.js | 57 ++-- app/packs/src/components/QuillEditor.js | 33 +- app/packs/src/components/QuillViewer.js | 10 +- .../ComputedPropsGraphContainer.js | 8 +- .../computedProps/SampleComputedProps.js | 9 +- .../container/ContainerComponent.js | 12 +- .../src/components/lineChart/EditableCell.js | 7 +- .../src/components/lineChart/EditableTable.js | 16 +- .../lineChart/LineChartContainer.js | 43 ++- .../components/metadata/MetadataContainer.js | 8 +- .../src/components/reactQuill/ReactQuill.js | 4 - .../ToolbarTemplateCreator.js | 7 +- app/packs/src/models/Reaction.js | 41 ++- 40 files changed, 730 insertions(+), 834 deletions(-) delete mode 100644 app/packs/src/apps/admin/textTemplates/TextTemplate.js create mode 100644 app/packs/src/apps/admin/textTemplates/TextTemplateForm.js create mode 100644 app/packs/src/apps/admin/textTemplates/TextTemplateSelector.js diff --git a/app/packs/src/apps/admin/textTemplates/TextTemplate.js b/app/packs/src/apps/admin/textTemplates/TextTemplate.js deleted file mode 100644 index bedf98c426..0000000000 --- a/app/packs/src/apps/admin/textTemplates/TextTemplate.js +++ /dev/null @@ -1,303 +0,0 @@ -import { AgGridReact } from 'ag-grid-react'; -import { cloneDeep } from 'lodash'; -import PropTypes from 'prop-types'; -import Delta from 'quill-delta'; -import React from 'react'; -import { Button, Form, Container, Col, InputGroup, Row } from 'react-bootstrap'; - -import QuillEditor from 'src/components/QuillEditor'; -import TextTemplateIcon from 'src/apps/admin/textTemplates/TextTemplateIcon'; - -function RemoveRowBtn({ removeRow, node }) { - const { data } = node; - - const btnClick = () => { - removeRow(data.name); - }; - - return ( - - ); -} - -RemoveRowBtn.propTypes = { - removeRow: PropTypes.func.isRequired, - // eslint-disable-next-line react/forbid-prop-types - node: PropTypes.object.isRequired, -}; - -function AddRowBtn({ addRow }) { - return ( - - ); -} - -AddRowBtn.propTypes = { - addRow: PropTypes.func.isRequired, -}; - -export default class TextTemplate extends React.Component { - constructor(props) { - super(props); - - this.state = { - selectedTemplate: null, - text: '', - icon: '' - }; - - this.reactQuillRef = React.createRef(); - - this.onSelectionChanged = this.onSelectionChanged.bind(this); - this.onGridReady = this.onGridReady.bind(this); - this.onCellValueChanged = this.onCellValueChanged.bind(this); - this.onChangeText = this.onChangeText.bind(this); - this.onChangeIcon = this.onChangeIcon.bind(this); - - this.saveTemplate = this.saveTemplate.bind(this); - this.removeRow = this.removeRow.bind(this); - this.addRow = this.addRow.bind(this); - - this.columnDefs = [ - { - field: 'name', - editable: true, - minWidth: 150, - onCellValueChanged: this.onCellValueChanged - }, - { - headerName: '', - colId: 'actions', - headerComponent: AddRowBtn, - headerComponentParams: { - addRow: this.addRow - }, - cellRenderer: RemoveRowBtn, - cellRendererParams: { - removeRow: this.removeRow, - }, - editable: false, - filter: false, - width: 35, - }, - ]; - } - - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(newProps) { - const { fetchedTemplates } = newProps; - const selectedRows = this.gridApi.getSelectedRows(); - if (selectedRows.length === 0) return; - - const selectedNameIdx = fetchedTemplates.findIndex(t => ( - t.name === selectedRows[0].name - )); - if (selectedNameIdx < 0) return; - - const newSelectedTemplate = cloneDeep(fetchedTemplates[selectedNameIdx]); - this.setState({ - selectedTemplate: cloneDeep(newSelectedTemplate), - text: newSelectedTemplate.data.text || '', - icon: newSelectedTemplate.data.icon || '' - }); - } - - componentDidUpdate() { - if (this.gridApi) { - this.gridApi.sizeColumnsToFit(); - } - } - - // eslint-disable-next-line class-methods-use-this - onGridReady(e) { - if (!e.api) return; - this.gridApi = e.api; - this.gridApi.sizeColumnsToFit(); - } - - onSelectionChanged() { - if (!this.gridApi) return; - - const selectedRows = this.gridApi.getSelectedRows(); - if (selectedRows.length === 0) return; - - const templateName = selectedRows[0].name; - const { fetchedTemplates } = this.props; - const selectedNameIdx = fetchedTemplates.findIndex(t => ( - t.name === templateName - )); - - if (selectedNameIdx >= 0) { - const selectedTemplate = cloneDeep(fetchedTemplates[selectedNameIdx]); - this.setState({ - selectedTemplate, - text: selectedTemplate.data.text || '', - icon: selectedTemplate.data.icon || '' - }); - } else { - const { fetchTemplate } = this.props; - fetchTemplate(templateName); - } - } - - onCellValueChanged(params) { - const { oldValue, newValue } = params; - const { fetchedTemplates, updateTemplate } = this.props; - const selectedNameIdx = fetchedTemplates.findIndex(t => ( - t.name === oldValue - )); - if (selectedNameIdx < 0) return; - - const selectedTemplate = cloneDeep(fetchedTemplates[selectedNameIdx]); - selectedTemplate.name = newValue; - updateTemplate(selectedTemplate); - } - - onChangeText(e) { - const { selectedTemplate } = this.state; - const { value } = e.target; - selectedTemplate.data.text = value; - - this.setState({ - text: value, - selectedTemplate - }); - } - - onChangeIcon(e) { - const { selectedTemplate } = this.state; - const { value } = e.target; - selectedTemplate.data.icon = value; - - this.setState({ icon: value, selectedTemplate }); - } - - saveTemplate() { - if (this.reactQuillRef.current == null) { - return; - } - - const quill = this.reactQuillRef.current; - const delta = quill.getContents(); - - // Quill automatically append a trailing newline, we don't want that - // Remove it !!! - const deltaLength = delta.length(); - const removeTrailingNewline = new Delta().retain(deltaLength - 1).delete(1); - const content = delta.compose(removeTrailingNewline); - - const selectedTemplate = cloneDeep(this.state.selectedTemplate); - selectedTemplate.data.ops = content.ops; - this.props.updateTemplate(selectedTemplate); - } - - removeRow(name) { - const { removeTemplate } = this.props; - removeTemplate(name); - this.setState({ selectedTemplate: null }); - } - - addRow() { - const { addTemplate } = this.props; - addTemplate(this.gridApi); - } - - handleInputChange = () => {} - - render() { - const { predefinedTemplateNames } = this.props; - const { selectedTemplate, text, icon } = this.state; - - return ( - - - -
-
- -
-
- - -
- - Preview - - -
- - Text - {}} - className='py-3' - /> - - - Icon - {}} - className='py-3' - /> - - - this.handleInputChange(event)} - /> - -
- -
- -
-
- ); - } -} - -TextTemplate.propTypes = { - predefinedTemplateNames: PropTypes.arrayOf(PropTypes.object), - // eslint-disable-next-line react/forbid-prop-types - fetchedTemplates: PropTypes.arrayOf(PropTypes.object), - fetchTemplate: PropTypes.func.isRequired, - addTemplate: PropTypes.func.isRequired, - updateTemplate: PropTypes.func.isRequired, - removeTemplate: PropTypes.func.isRequired, -}; - -TextTemplate.defaultProps = { - predefinedTemplateNames: [], - fetchedTemplates: [], -}; diff --git a/app/packs/src/apps/admin/textTemplates/TextTemplateContainer.js b/app/packs/src/apps/admin/textTemplates/TextTemplateContainer.js index 1cf6aabbb1..4a56e1ed06 100644 --- a/app/packs/src/apps/admin/textTemplates/TextTemplateContainer.js +++ b/app/packs/src/apps/admin/textTemplates/TextTemplateContainer.js @@ -1,103 +1,158 @@ import React from 'react'; +import { Container, Col, Row } from 'react-bootstrap'; + import TextTemplatesFetcher from 'src/fetchers/TextTemplatesFetcher'; -import TextTemplate from 'src/apps/admin/textTemplates/TextTemplate'; +import TextTemplateForm from 'src/apps/admin/textTemplates/TextTemplateForm'; +import TextTemplateSelector from 'src/apps/admin/textTemplates/TextTemplateSelector'; + export default class TextTemplateContainer extends React.Component { constructor(props) { super(props); this.state = { - predefinedTemplateNames: [], - fetchedTemplates: [], + templateNames: [], + fetchedTemplates: {}, + selectedTemplateName: null, }; this.addTemplate = this.addTemplate.bind(this); - this.fetchTemplate = this.fetchTemplate.bind(this); + this.renameTemplate = this.renameTemplate.bind(this); this.removeTemplate = this.removeTemplate.bind(this); this.updateTemplate = this.updateTemplate.bind(this); + this.selectTemplate = this.selectTemplate.bind(this); } componentDidMount() { TextTemplatesFetcher.fetchPredefinedTemplateNames().then((res) => { - const templateNames = res.map(n => ({ name: n })); - this.setState({ predefinedTemplateNames: templateNames }); + this.setState({ templateNames: res }); }); } addTemplate(gridApi) { - const { predefinedTemplateNames, fetchedTemplates } = this.state; + const { templateNames, fetchedTemplates } = this.state; this.setState({ - predefinedTemplateNames: [{ name: '' }, ...predefinedTemplateNames], - fetchedTemplates: [ - { name: '', data: {} }, - ...fetchedTemplates - ] + templateNames: ['', ...templateNames], + fetchedTemplates: { + ...fetchedTemplates, + '': { name: '', data: {} }, + }, + selectedTemplateName: '', }, () => { - if (!gridApi) return; - - gridApi.startEditingCell({ rowIndex: 0, colKey: 'name' }); + if (gridApi) { + gridApi.startEditingCell({ rowIndex: 0, colKey: 'name' }); + } }); } - fetchTemplate(name) { - TextTemplatesFetcher.fetchPredefinedTemplateByNames([name]).then((res) => { + selectTemplate(name) { + const { fetchedTemplates, selectedTemplateName } = this.state; + if (name === selectedTemplateName && fetchedTemplates[name]) return; + + if (fetchedTemplates[name]) { + this.setState({ selectedTemplateName: name }); + } else { + TextTemplatesFetcher.fetchPredefinedTemplateByNames([name]).then((res) => { + if (!res) return; + + const newTemplates = {}; + res.forEach((r) => newTemplates[r.name] = r); + + this.setState({ + fetchedTemplates: { + ...fetchedTemplates, + ...newTemplates, + }, + selectedTemplateName: name, + }); + }); + } + } + + removeTemplate(name) { + TextTemplatesFetcher.deletePredefinedTemplateByName(name).then((res) => { if (!res) return; - const { fetchedTemplates } = this.state; + const { templateNames, fetchedTemplates, selectedTemplateName } = this.state; + const newFetchedTemplates = { ...fetchedTemplates }; + delete newFetchedTemplates[name]; + this.setState({ - fetchedTemplates: [...fetchedTemplates].concat(res), + templateNames: templateNames.filter((n) => n !== name), + fetchedTemplates: newFetchedTemplates, + selectedTemplateName: selectedTemplateName === name ? null : selectedTemplateName, }); }); } - removeTemplate(name) { - TextTemplatesFetcher.deletePredefinedTemplateByName(name).then((res) => { + renameTemplate(oldName, newName) { + const { templateNames, fetchedTemplates, selectedTemplateName } = this.state; + const template = fetchedTemplates[oldName]; + + const newTemplate = {...template, name: newName}; + const newFetchedTemplates = { ...fetchedTemplates, [newName]: newTemplate }; + delete newFetchedTemplates[oldName]; + + TextTemplatesFetcher.updatePredefinedTemplates(newTemplate).then((res) => { if (!res) return; - const { fetchedTemplates, predefinedTemplateNames } = this.state; this.setState({ - fetchedTemplates: fetchedTemplates.filter(t => t.name !== name), - predefinedTemplateNames: predefinedTemplateNames.filter(t => ( - t.name !== name - )) + templateNames: templateNames.with(templateNames.indexOf(oldName), newName), + fetchedTemplates: newFetchedTemplates, + selectedTemplateName: selectedTemplateName === oldName ? newName : selectedTemplateName, }); }); } updateTemplate(template) { const { fetchedTemplates } = this.state; - const selectedNameIdx = fetchedTemplates.findIndex(t => ( - t.id === template.id - )); - if (selectedNameIdx < 0) return; - TextTemplatesFetcher.updatePredefinedTemplates(template).then((res) => { if (!res) return; - const newTemplates = fetchedTemplates.map((t, idx) => ( - (idx === selectedNameIdx) ? res : t - )); - this.setState({ fetchedTemplates: newTemplates }); + this.setState({ + fetchedTemplates: { + ...fetchedTemplates, + [template.name]: template + } + }); }); } render() { const { - predefinedTemplateNames, + templateNames, fetchedTemplates, + selectedTemplateName, } = this.state; + const selectedTemplate = fetchedTemplates[selectedTemplateName]; + return ( - + + + + + + + {selectedTemplate ? ( + + ) : ( +

Select a template to edit

+ )} + +
+
); } } diff --git a/app/packs/src/apps/admin/textTemplates/TextTemplateForm.js b/app/packs/src/apps/admin/textTemplates/TextTemplateForm.js new file mode 100644 index 0000000000..a94e23966d --- /dev/null +++ b/app/packs/src/apps/admin/textTemplates/TextTemplateForm.js @@ -0,0 +1,129 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Delta from 'quill-delta'; +import { Button, Form, InputGroup } from 'react-bootstrap'; + +import QuillEditor from 'src/components/QuillEditor'; +import TextTemplateIcon from 'src/apps/admin/textTemplates/TextTemplateIcon'; + + +export default class TextTemplateForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + text: props.selectedTemplate?.data?.text ?? '', + icon: props.selectedTemplate?.data?.icon ?? '', + }; + + this.reactQuillRef = React.createRef(); + + this.onChangeText = this.onChangeText.bind(this); + this.onChangeIcon = this.onChangeIcon.bind(this); + this.saveTemplate = this.saveTemplate.bind(this); + + } + + componentDidUpdate(prevProps) { + const { selectedTemplate } = this.props; + if (selectedTemplate !== prevProps.selectedTemplate) { + this.setState({ + text: selectedTemplate?.data?.text ?? '', + icon: selectedTemplate?.data?.icon ?? '', + }); + } + } + + onChangeText(e) { + const { value } = e.target; + this.setState({ text: value }); + } + + onChangeIcon(e) { + const { value } = e.target; + this.setState({ icon: value }); + } + + saveTemplate() { + if (this.reactQuillRef.current == null) { + return; + } + + const quill = this.reactQuillRef.current; + const delta = quill.getContents(); + + // Quill automatically append a trailing newline, we don't want that + // Remove it !!! + const deltaLength = delta.length(); + const removeTrailingNewline = new Delta().retain(deltaLength - 1).delete(1); + const { ops } = delta.compose(removeTrailingNewline); + + const { selectedTemplate, updateTemplate } = this.props; + const { text, icon } = this.state; + updateTemplate({ + ...selectedTemplate, + data: { + ops, + text: text.trim() == '' ? null : text.trim(), + icon: icon.trim() == '' ? null : icon.trim(), + } + }); + } + + render() { + const { selectedTemplate } = this.props; + const { text, icon } = this.state; + + return ( +
+

Editing '{selectedTemplate.name}'

+ + Preview + + +
+ + Text + + + + Icon + + + + {}} + /> + +
+ +
+ ); + } +} + +TextTemplateForm.propTypes = { + selectedTemplate: PropTypes.object, + updateTemplate: PropTypes.func.isRequired, +}; + +TextTemplateForm.defaultProps = { + selectedTemplate: null, +}; diff --git a/app/packs/src/apps/admin/textTemplates/TextTemplateIcon.js b/app/packs/src/apps/admin/textTemplates/TextTemplateIcon.js index 6766e368e7..3f3736a947 100644 --- a/app/packs/src/apps/admin/textTemplates/TextTemplateIcon.js +++ b/app/packs/src/apps/admin/textTemplates/TextTemplateIcon.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; const TextTemplateIcon = ({ template, iconClass }) => { - if (!template) return ; + if (!template) return null; const { data, name } = template; @@ -12,7 +12,7 @@ const TextTemplateIcon = ({ template, iconClass }) => { ); } - const text = (data || {}).text || name; + const text = data?.text ?? name ?? ''; return ( {text.toUpperCase()} diff --git a/app/packs/src/apps/admin/textTemplates/TextTemplateSelector.js b/app/packs/src/apps/admin/textTemplates/TextTemplateSelector.js new file mode 100644 index 0000000000..2863a21868 --- /dev/null +++ b/app/packs/src/apps/admin/textTemplates/TextTemplateSelector.js @@ -0,0 +1,128 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types'; +import { Button } from 'react-bootstrap'; +import { AgGridReact } from 'ag-grid-react'; + +function RemoveRowBtn({ removeRow, node }) { + const { data } = node; + + const btnClick = () => { + removeRow(data.name); + }; + + return ( + + ); +} + +RemoveRowBtn.propTypes = { + removeRow: PropTypes.func.isRequired, + // eslint-disable-next-line react/forbid-prop-types + node: PropTypes.object.isRequired, +}; + +function AddRowBtn({ addRow }) { + return ( + + ); +} + +AddRowBtn.propTypes = { + addRow: PropTypes.func.isRequired, +}; + +class TextTemplateSelector extends Component { + constructor(props) { + super(props); + } + + componentDidMount() { + this.gridApi?.sizeColumnsToFit() + } + + componentDidUpdate() { + this.gridApi?.sizeColumnsToFit() + } + + onSelectionChanged = ({api}) => { + const selectedRows = api.getSelectedRows(); + if (selectedRows.length === 0) return; + + const { selectTemplate } = this.props; + selectTemplate(selectedRows[0].name); + } + + onGridReady = ({api}) => { + this.gridApi = api; + } + + render() { + const { + templateNames, + addTemplate, + renameTemplate, + removeTemplate + } = this.props; + + const columnDefs = [ + { + field: 'name', + editable: true, + minWidth: 150, + onCellValueChanged: ({ oldValue, newValue }) => { + renameTemplate(oldValue, newValue); + } + }, + { + headerName: '', + colId: 'actions', + headerComponent: AddRowBtn, + headerComponentParams: { + addRow: addTemplate, + }, + cellRenderer: RemoveRowBtn, + cellRendererParams: { + removeRow: removeTemplate, + }, + editable: false, + filter: false, + width: 35, + }, + ]; + + return ( +
+
+ ({ name: n }))} + className='fs-6' + rowHeight={35} + /> +
+
+ ); + } +}; + +export default TextTemplateSelector; diff --git a/app/packs/src/apps/mydb/collections/CollectionSubtree.js b/app/packs/src/apps/mydb/collections/CollectionSubtree.js index 17b6486487..90588b3187 100644 --- a/app/packs/src/apps/mydb/collections/CollectionSubtree.js +++ b/app/packs/src/apps/mydb/collections/CollectionSubtree.js @@ -15,11 +15,7 @@ export default class CollectionSubtree extends React.Component { super(props); this.state = { - isRemote: props.isRemote, - label: props.root.label, - inventoryPrefix: props.root.inventory_prefix, selected: false, - root: props.root, visible: false } @@ -29,48 +25,31 @@ export default class CollectionSubtree extends React.Component { this.handleTakeOwnership = this.handleTakeOwnership.bind(this) } - componentDidMount() { UIStore.listen(this.onChange); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - root: nextProps.root, - label: nextProps.root.label, - inventoryPrefix: nextProps.root.inventory_prefix - }); - } - componentWillUnmount() { UIStore.unlisten(this.onChange); } onChange(state) { - if (state.currentCollection) { - const visible = this.isVisible(this.state.root, state) - const { root } = this.state; + const { root } = this.props; + if (state.currentCollection) { + const visible = this.isVisible(root, state) const selectedCol = ( - state.currentCollection.id == root.id && - state.currentCollection.is_synchronized == root.is_synchronized - ) || ( - state.currentCollection.id == root.id && - state.currentCollection.isRemote == root.isRemote + state.currentCollection.id == root.id + && ( + state.currentCollection.is_synchronized == root.is_synchronized + || state.currentCollection.isRemote == root.isRemote ) + ); - if (selectedCol) { - this.setState({ - selected: true, - visible - }); - } else { - this.setState({ - selected: false, + this.setState({ + selected: selectedCol, visible - }); - } + }); } } @@ -85,68 +64,58 @@ export default class CollectionSubtree extends React.Component { } children() { - return this.state.root.children || []; - } - - hasChildren() { - return this.children().length > 0; + const { root } = this.props; + return root.children || []; } canTakeOwnership() { - const { root, isRemote } = this.state; + const { root, isRemote } = this.props; const isTakeOwnershipAllowed = root.permission_level === 5; const isSync = !!((root.sharer && root.user && root.user.type !== 'Group')); return (isRemote || isSync) && isTakeOwnershipAllowed; } handleTakeOwnership() { - const { root: { sharer, id } } = this.state; + const { root: { sharer, id } } = this.props; const isSync = !!sharer; CollectionActions.takeOwnership({ id, isSync }); } handleClick(e) { - const { fakeRoot } = this.props; - if (fakeRoot) { - e.stopPropagation(); - return; - } - - const { root } = this.state; - let { visible } = this.state; + const { root } = this.props; + const { visible } = this.state; const uiState = UIStore.getState(); + this.setState({ visible: visible || this.isVisible(root, uiState) }); - visible = visible || this.isVisible(root, uiState); - this.setState({ visible }); - let collectionID = 'all'; if (root.label === 'All' && root.is_locked) { Aviator.navigate(`/collection/all/${this.urlForCurrentElement()}`, { silent: true }); - collectionShow({ params: { collectionID } }); + collectionShow({ params: { collectionID: 'all' } }); return; } - const url = (this.props.root.sharer) + + const url = (root.sharer) ? `/scollection/${root.id}/${this.urlForCurrentElement()}` : `/collection/${root.id}/${this.urlForCurrentElement()}`; Aviator.navigate(url, { silent: true }); - collectionID = this.state.root.id; - const collShow = this.props.root.sharer ? scollectionShow : collectionShow; - collShow({ params: { collectionID } }); + + const collShow = root.sharer ? scollectionShow : collectionShow; + collShow({ params: { collectionID: root.id } }); } urlForCurrentElement() { const { currentElement } = ElementStore.getState(); if (currentElement) { - if (currentElement.isNew) { - return `${currentElement.type}/new`; - } - return `${currentElement.type}/${currentElement.id}`; + return currentElement.isNew + ? `${currentElement.type}/new` + : `${currentElement.type}/${currentElement.id}`; } return ''; } toggleExpansion(e) { e.stopPropagation() - let { visible, root } = this.state + const { root } = this.props; + let { visible } = this.state visible = !visible this.setState({ visible: visible }) @@ -156,7 +125,7 @@ export default class CollectionSubtree extends React.Component { } else { let descendantIds = root.descendant_ids ? root.descendant_ids - : root.children.map(function (s) { return s.id }) + : root.children.map((s) => s.id); descendantIds.push(root.id) visibleRootsIds = visibleRootsIds.filter(x => descendantIds.indexOf(x) == -1) } @@ -167,11 +136,12 @@ export default class CollectionSubtree extends React.Component { } render() { - const { label, root, inventoryPrefix, visible, selected } = this.state; + const { root, isRemote } = this.props; + const { visible, selected } = this.state; const sharedUsers = root.sync_collections_users; const children = this.children(); - const showGatePushButton = root && root.is_locked && label === 'chemotion-repository.net'; + const showGatePushButton = root && root.is_locked && root.label === 'chemotion-repository.net'; return (
@@ -181,13 +151,13 @@ export default class CollectionSubtree extends React.Component { onClick={this.handleClick} > {showGatePushButton && ()} - {label} - {inventoryPrefix && ( + {root.label} + {root.inventory_prefix && ( Inventory Label} > - {inventoryPrefix} + {root.inventory_prefix} )} {this.canTakeOwnership() && ( @@ -201,7 +171,7 @@ export default class CollectionSubtree extends React.Component { )} - {this.hasChildren() && ( + {children.length > 0 && ( {children.map((child) => (
  • - +
  • ))} diff --git a/app/packs/src/apps/mydb/elements/details/NumeralInput.js b/app/packs/src/apps/mydb/elements/details/NumeralInput.js index d789bce3b5..30a007dd86 100644 --- a/app/packs/src/apps/mydb/elements/details/NumeralInput.js +++ b/app/packs/src/apps/mydb/elements/details/NumeralInput.js @@ -13,12 +13,13 @@ export default class NumeralInput extends Component { }; } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - const { value } = nextProps; - this.setState({ - numeralValue: this._convertValueToNumeralValue(value) - }); + componentDidUpdate(prevProps) { + const { value } = this.props; + if (value !== prevProps.value) { + this.setState({ + numeralValue: this._convertValueToNumeralValue(value) + }); + } } _convertValueToNumeralValue(value) { diff --git a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js index 5f29f7edb7..e1bc89da43 100644 --- a/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js +++ b/app/packs/src/apps/mydb/elements/details/NumeralInputWithUnitsCompo.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { isEqual } from 'lodash'; import { Form, InputGroup, Button } from 'react-bootstrap'; import { metPreConv, metPrefSymbols } from 'src/utilities/metricPrefix'; @@ -22,9 +23,12 @@ export default class NumeralInputWithUnitsCompo extends Component { this.forceUpdate(); } - UNSAFE_componentWillReceiveProps(nextProps) { - const { value, block } = nextProps; - this.setState({ value, block }); + componentDidUpdate(prevProps) { + const { value, block } = this.props; + // isEqual considers NaN to be equal to NaN + if (!isEqual(value, prevProps.value) || block !== prevProps.block) { + this.setState({ value, block }); + } } shouldComponentUpdate(nextProps, nextState) { diff --git a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js index 98f039469b..165ea1f7cc 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js @@ -96,20 +96,13 @@ export default class ReactionDetails extends Component { } } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - const { reaction } = this.state; - const nextReaction = nextProps.reaction; - - if (nextReaction.id !== reaction.id || - nextReaction.updated_at !== reaction.updated_at || - nextReaction.reaction_svg_file !== reaction.reaction_svg_file || - nextReaction.changed || nextReaction.editedSample) { - this.setState(prevState => ({ ...prevState, reaction: nextReaction })); + componentDidUpdate(prevProps) { + const { reaction } = this.props; + if (reaction.id !== prevProps.reaction.id) { + this.setState({ reaction }); } } - shouldComponentUpdate(nextProps, nextState) { const reactionFromNextProps = nextProps.reaction; const reactionFromNextState = nextState.reaction; diff --git a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetailsMainProperties.js b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetailsMainProperties.js index 404c8beb84..150cc39938 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetailsMainProperties.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetailsMainProperties.js @@ -20,30 +20,18 @@ import { permitOn } from 'src/components/common/uis'; export default class ReactionDetailsMainProperties extends Component { constructor(props) { super(props); - const { temperature } = props && props.reaction; + this.state = { showTemperatureChart: false, - temperature, }; + this.toggleTemperatureChart = this.toggleTemperatureChart.bind(this); this.updateTemperature = this.updateTemperature.bind(this); - this.temperatureUnit = props.reaction.temperature.valueUnit; - } - - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - temperature: nextProps.reaction.temperature, - }); - - this.temperatureUnit = nextProps.reaction.temperature.valueUnit; } updateTemperature(newData) { - const { temperature } = this.state; - temperature.data = newData; - this.setState({ temperature }); - this.props.onInputChange('temperatureData', temperature); + const { reaction: { temperature } } = this.props; + this.props.onInputChange('temperatureData', { ...temperature, data: newData }); } toggleTemperatureChart() { @@ -52,42 +40,18 @@ export default class ReactionDetailsMainProperties extends Component { } changeUnit() { - const index = Reaction.temperature_unit.indexOf(this.temperatureUnit); - const unit = Reaction.temperature_unit[(index + 1) % 3]; + const { reaction: { temperature } } = this.props; + + const units = Reaction.temperature_unit; + const index = units.indexOf(temperature.valueUnit); + const unit = units[(index + 1) % units.length]; this.props.onInputChange('temperatureUnit', unit); } - render() { const { reaction, onInputChange } = this.props; - const temperatureTooltip = ( - Show temperature chart - ); - - const temperatureDisplay = reaction.temperature_display; - const { showTemperatureChart, temperature } = this.state; - const tempUnitLabel = `Temperature (${this.temperatureUnit})`; - - let TempChartRow = ; - if (showTemperatureChart) { - TempChartRow = ( - - - - - - - - - ); - } + const { temperature } = reaction; + const { showTemperatureChart } = this.state; return ( <> @@ -126,7 +90,9 @@ export default class ReactionDetailsMainProperties extends Component { Temperature - + Show temperature chart + )}> - - {TempChartRow} - + + {showTemperatureChart && ( + + + + + + + + + )} ); } diff --git a/app/packs/src/apps/mydb/elements/details/reactions/analysesTab/ReactionDetailsContainers.js b/app/packs/src/apps/mydb/elements/details/reactions/analysesTab/ReactionDetailsContainers.js index d6814949dd..02af5d1b52 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/analysesTab/ReactionDetailsContainers.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/analysesTab/ReactionDetailsContainers.js @@ -60,15 +60,13 @@ const nmrMsg = (reaction, container) => { export default class ReactionDetailsContainers extends Component { constructor(props) { - super(); - const { reaction } = props; + super(props); + this.state = { - reaction, activeContainer: UIStore.getState().reaction.activeAnalysis }; this.containerRefs = {}; - this.handleChange = this.handleChange.bind(this); this.handleAdd = this.handleAdd.bind(this); this.handleRemove = this.handleRemove.bind(this); this.handleUndo = this.handleUndo.bind(this); @@ -100,16 +98,8 @@ export default class ReactionDetailsContainers extends Component { } } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - reaction: nextProps.reaction, - }); - } - handleChange = () => { - const { handleReactionChange } = this.props; - const { reaction } = this.state; + const { reaction, handleReactionChange } = this.props; handleReactionChange(reaction); }; @@ -120,15 +110,13 @@ export default class ReactionDetailsContainers extends Component { } handleUndo(container) { - const { handleReactionChange } = this.props; - const { reaction } = this.state; + const { reaction, handleReactionChange } = this.props; container.is_deleted = false; handleReactionChange(reaction, { schemaChanged: false }); } handleAdd() { - const { handleReactionChange } = this.props; - const { reaction } = this.state; + const { reaction, handleReactionChange } = this.props; const container = Container.buildEmpty(); container.container_type = 'analysis'; container.extended_metadata.content = { ops: [{ insert: '' }] }; @@ -218,9 +206,7 @@ export default class ReactionDetailsContainers extends Component { handleRemove(container) { - const { handleReactionChange } = this.props; - const { reaction } = this.state; - + const { reaction, handleReactionChange } = this.props; container.is_deleted = true; handleReactionChange(reaction, { schemaChanged: false }); } @@ -249,8 +235,8 @@ export default class ReactionDetailsContainers extends Component { } render() { - const { reaction, activeContainer } = this.state; - const { readOnly } = this.props; + const { activeContainer } = this.state; + const { reaction, readOnly } = this.props; const containerHeader = (container) => { let kind = container.extended_metadata.kind || ''; @@ -371,7 +357,9 @@ export default class ReactionDetailsContainers extends Component { > - {container.is_deleted ? containerHeaderDeleted(container) : containerHeader(container)} + {container.is_deleted + ? containerHeaderDeleted(container) + : containerHeader(container)} diff --git a/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js b/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js index 5895b61eea..c763996dec 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js @@ -16,18 +16,11 @@ import { EditUserLabels } from 'src/components/UserLabels'; export default class ReactionDetailsProperties extends Component { constructor(props) { super(props); - props.reaction.convertDurationDisplay(); this.handleOnReactionChange = this.handleOnReactionChange.bind(this); this.handleOnSolventSelect = this.handleOnSolventSelect.bind(this); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - if (!nextProps.reaction) { return; } - nextProps.reaction.convertDurationDisplay(); - } - handleOnReactionChange(reaction) { this.props.onReactionChange(reaction); } diff --git a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsDuration.js b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsDuration.js index 38177dde7d..f4dab39dd6 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsDuration.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsDuration.js @@ -8,18 +8,11 @@ import { copyToClipboard } from 'src/utilities/clipboard'; export default class ReactionDetailsDuration extends Component { constructor(props) { super(props); - props.reaction.convertDurationDisplay(); this.setCurrentTime = this.setCurrentTime.bind(this); this.copyToDuration = this.copyToDuration.bind(this); this.handleDurationChange = this.handleDurationChange.bind(this); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - if (!nextProps.reaction) { return; } - nextProps.reaction.convertDurationDisplay(); - } - setCurrentTime(type) { const currentTime = new Date().toLocaleString('en-GB').split(', ').join(' '); const { reaction } = this.props; diff --git a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsScheme.js b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsScheme.js index c10c2b3d89..61a3c780a4 100644 --- a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsScheme.js +++ b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/ReactionDetailsScheme.js @@ -40,11 +40,8 @@ export default class ReactionDetailsScheme extends Component { constructor(props) { super(props); - const { reaction } = props; - const textTemplate = TextTemplateStore.getState().reactionDescription; this.state = { - reaction, lockEquivColumn: false, reactionDescTemplate: textTemplate.toJS(), }; @@ -76,12 +73,6 @@ export default class ReactionDetailsScheme extends Component { TextTemplateActions.fetchTextTemplates('reactionDescription'); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - const { reaction } = nextProps; - this.setState({ reaction }); - } - componentWillUnmount() { TextTemplateStore.unlisten(this.handleTemplateChange); this.resetGasPhaseStore(); @@ -93,7 +84,7 @@ export default class ReactionDetailsScheme extends Component { } dropSample(srcSample, tagMaterial, tagGroup, extLabel, isNewSample = false) { - const { reaction } = this.state; + const { reaction } = this.props; let splitSample; if (srcSample instanceof Molecule || isNewSample) { @@ -165,11 +156,12 @@ export default class ReactionDetailsScheme extends Component { } renderRoleSelect() { - const { role } = this.props.reaction; + const { reaction } = this.props; + const { role } = reaction; return (