Skip to content

Commit

Permalink
feat: label the molecule
Browse files Browse the repository at this point in the history
close #1342
  • Loading branch information
hamed-musallam committed May 31, 2022
1 parent d562c7f commit 907d09a
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 30 deletions.
16 changes: 11 additions & 5 deletions src/component/modal/MoleculeStructureEditorModal.tsx
Expand Up @@ -13,10 +13,7 @@ import { ModalStyles } from './ModalStyle';

interface MoleculeStructureEditorModalProps {
onClose?: (element?: string) => void;
selectedMolecule?: {
key: any;
molfile: string;
};
selectedMolecule?: Molecule;
}

function MoleculeStructureEditorModal(
Expand Down Expand Up @@ -52,7 +49,16 @@ function MoleculeStructureEditorModal(

const handleSave = useCallback(() => {
if (selectedMolecule) {
dispatch({ type: SET_MOLECULE, molfile, key: selectedMolecule.key });
const { key, isFloat, label } = selectedMolecule;
dispatch({
type: SET_MOLECULE,
payload: {
molfile,
key,
isFloat,
label,
},
});
onClose('replace');
} else {
dispatch({ type: ADD_MOLECULE, molfile });
Expand Down
74 changes: 74 additions & 0 deletions src/component/panels/MoleculesPanel/MoleculeHeader.tsx
@@ -0,0 +1,74 @@
import { useCallback, CSSProperties } from 'react';
import { MF } from 'react-mf';

import { Molecule } from '../../../data/molecules/Molecule';
import {
extractLabelsNumbers,
extractNumber,
} from '../../../data/molecules/MoleculeManager';
import { useDispatch } from '../../context/DispatchContext';
import EditableColumn from '../../elements/EditableColumn';
import { InputKeyboardEvent } from '../../elements/Input';
import { CHANGE_MOLECULE_LABEL } from '../../reducer/types/Types';

interface MoleculeHeaderProps {
currentMolecule: Molecule;
molecules: Molecule[];
}

const styles: Record<'toolbar' | 'labelInput', CSSProperties> = {
toolbar: {
display: 'flex',
borderBottom: '0.55px solid rgb(240, 240, 240)',
padding: '5px 10px',
justifyContent: 'space-between',
height: '35px',
},
labelInput: {
width: '150px',
border: '1px solid #f4f4f4',
borderRadius: '5px',
cursor: 'pointer',
textAlign: 'center',
marginLeft: '5px',
},
};

export default function MoleculeHeader(props: MoleculeHeaderProps) {
const { currentMolecule, molecules } = props;
const dispatch = useDispatch();

const validateLabel = useCallback(
(value) => {
const reservedNumbers = extractLabelsNumbers(molecules);
const number = extractNumber(value);
return value && !reservedNumbers.includes(Number(number));
},
[molecules],
);
const saveLabelHandler = useCallback(
(key: string, event: InputKeyboardEvent) => {
const label = event.target.value;
dispatch({ type: CHANGE_MOLECULE_LABEL, payload: { label, key } });
},
[dispatch],
);

return (
<div style={styles.toolbar}>
<div style={{ display: 'flex' }}>
<span>Label</span>
<EditableColumn
value={currentMolecule.label}
style={styles.labelInput}
validate={validateLabel}
onSave={(event) => saveLabelHandler(currentMolecule.key, event)}
/>
</div>

<span>
<MF mf={currentMolecule.mf} /> - {currentMolecule.mw?.toFixed(2)}
</span>
</div>
);
}
24 changes: 12 additions & 12 deletions src/component/panels/MoleculesPanel/MoleculePanel.tsx
Expand Up @@ -3,7 +3,6 @@ import { css, SerializedStyles } from '@emotion/react';
import OCL from 'openchemlib/full';
import { useState, useCallback, useEffect, memo, ReactElement } from 'react';
import { ResponsiveChart } from 'react-d3-utils';
import { MF } from 'react-mf';
import OCLnmr from 'react-ocl-nmr';

import { Molecule } from '../../../data/molecules/Molecule';
Expand All @@ -17,6 +16,7 @@ import { useMoleculeEditor } from '../../modal/MoleculeStructureEditorModal';
import { DISPLAYER_MODE } from '../../reducer/core/Constants';
import { SET_MOLECULE } from '../../reducer/types/Types';

import MoleculeHeader from './MoleculeHeader';
import MoleculePanelHeader, {
MoleculeHeaderActionsOptions,
} from './MoleculePanelHeader';
Expand Down Expand Up @@ -47,9 +47,9 @@ const styles: Record<
toolbar: css({
display: 'flex',
borderBottom: '0.55px solid rgb(240, 240, 240)',
padding: '0px 10px',
justifyContent: 'end',
height: 22,
padding: '5px 10px',
justifyContent: 'space-between',
height: '35px',
}),
slider: css({
display: 'flex',
Expand Down Expand Up @@ -115,8 +115,12 @@ function MoleculePanelInner({
}, [currentIndex, molecules, onMoleculeChange]);

const handleReplaceMolecule = useCallback(
(key, molfile) => {
dispatch({ type: SET_MOLECULE, molfile, key });
(molecule, molfile) => {
const { key, label, isFloat } = molecule;
dispatch({
type: SET_MOLECULE,
payload: { molfile, key, label, isFloat },
});
},
[dispatch],
);
Expand All @@ -143,11 +147,7 @@ function MoleculePanelInner({
{molecules && molecules.length > 0 ? (
molecules.map((mol: Molecule, index) => (
<div key={mol.key} css={styles.items}>
<div css={styles.toolbar}>
<span>
<MF mf={mol.mf} /> - {mol.mw?.toFixed(2)}
</span>
</div>
<MoleculeHeader currentMolecule={mol} molecules={molecules} />
<div
css={styles.slider}
className="mol-svg-container"
Expand All @@ -167,7 +167,7 @@ function MoleculePanelInner({
height={height}
molfile={mol.molfile || ''}
setMolfile={(molfile) =>
handleReplaceMolecule(mol.key, molfile)
handleReplaceMolecule(mol, molfile)
}
setSelectedAtom={handleOnClickAtom}
atomHighlightColor={
Expand Down
8 changes: 3 additions & 5 deletions src/component/reducer/Reducer.ts
Expand Up @@ -609,11 +609,9 @@ function innerSpectrumReducer(draft: Draft<State>, action) {
return MoleculeActions.addMoleculeHandler(draft, action.molfile);

case types.SET_MOLECULE:
return MoleculeActions.setMoleculeHandler(
draft,
action.molfile,
action.key,
);
return MoleculeActions.setMoleculeHandler(draft, action);
case types.CHANGE_MOLECULE_LABEL:
return MoleculeActions.changeMoleculeLabel(draft, action);

case types.DELETE_MOLECULE:
return MoleculeActions.deleteMoleculeHandler(draft, action);
Expand Down
13 changes: 11 additions & 2 deletions src/component/reducer/actions/MoleculeActions.ts
Expand Up @@ -27,8 +27,9 @@ function addMoleculeHandler(draft: Draft<State>, molfile) {
}
}

function setMoleculeHandler(draft: Draft<State>, molfile, key) {
MoleculeManager.setMolfile(draft.molecules, molfile, key);
function setMoleculeHandler(draft: Draft<State>, action) {
const { key, label, molfile, isFloat } = action.payload;
MoleculeManager.setMolfile(draft.molecules, { key, label, molfile, isFloat });

/**
* update all spectra that its sum was based on this molecule with the new molecule
Expand Down Expand Up @@ -94,11 +95,19 @@ function floatMoleculeOverSpectrum(draft: Draft<State>, action) {
draft.molecules[moleculeIndex].isFloat =
!draft.molecules[moleculeIndex].isFloat;
}
function changeMoleculeLabel(draft: Draft<State>, action) {
const { key, label } = action.payload;
const moleculeIndex = draft.molecules.findIndex(
(molecule) => molecule.key === key,
);
draft.molecules[moleculeIndex].label = label;
}

export {
addMoleculeHandler,
setMoleculeHandler,
deleteMoleculeHandler,
predictSpectraFromMoleculeHandler,
floatMoleculeOverSpectrum,
changeMoleculeLabel,
};
3 changes: 2 additions & 1 deletion src/component/reducer/types/Types.ts
Expand Up @@ -37,6 +37,8 @@ export const SET_ZOOM = 'SET_ZOOM';
export const ADD_MOLECULE = 'ADD_MOLECULE';
export const SET_MOLECULE = 'SET_MOLECULE';
export const DELETE_MOLECULE = 'DELETE_MOLECULE';
export const FLOAT_MOLECULE_OVER_SPECTRUM = 'FLOAT_MOLECULE_OVER_SPECTRUM';
export const CHANGE_MOLECULE_LABEL = 'CHANGE_MOLECULE_LABEL';
export const SET_CORRELATION = 'SET_CORRELATION';
export const SET_CORRELATIONS = 'SET_CORRELATIONS';
export const DELETE_CORRELATION = 'DELETE_CORRELATION';
Expand Down Expand Up @@ -130,4 +132,3 @@ export const RESURRECTING_SPECTRUM_FROM_RANGES =
'RESURRECTING_SPECTRUM_FROM_RANGES';
export const SHOW_J_GRAPH = 'SHOW_J_GRAPH';
export const SET_AUTOMATIC_ASSIGNMENTS = 'SET_AUTOMATIC_ASSIGNMENTS';
export const FLOAT_MOLECULE_OVER_SPECTRUM = 'FLOAT_MOLECULE_OVER_SPECTRUM';
11 changes: 9 additions & 2 deletions src/data/molecules/Molecule.ts
Expand Up @@ -7,6 +7,7 @@ interface MoleculeInnerProps {
key: string;
molfile: string;
isFloat: boolean;
label: string;
}
export interface Molecule extends MoleculeInnerProps {
mf: string;
Expand All @@ -20,6 +21,7 @@ export function initMolecule(
options: Partial<MoleculeInnerProps> = {},
): Molecule {
const key = options.key || generateID();
const label = options.label || 'p#';
const molfile = options.molfile || '';
const isFloat =
typeof options?.isFloat === 'boolean' ? options.isFloat : false;
Expand All @@ -30,6 +32,7 @@ export function initMolecule(
return {
key,
molfile,
label,
mf: mfInfo.formula,
em: mfInfo.absoluteWeight,
mw: mfInfo.relativeWeight,
Expand All @@ -39,8 +42,12 @@ export function initMolecule(
};
}

export function toJSON(molecule: Molecule): { molfile: string } {
export function toJSON(
molecule: Molecule,
): Pick<MoleculeInnerProps, 'molfile' | 'label'> {
const { molfile, label } = molecule;
return {
molfile: molecule.molfile,
molfile,
label,
};
}
47 changes: 44 additions & 3 deletions src/data/molecules/MoleculeManager.ts
Expand Up @@ -3,6 +3,8 @@ import { Molecule as OCLMolecule } from 'openchemlib/full';
import { initMolecule, Molecule } from './Molecule';

export function fromJSON(mols: any[] = []) {
const reservedNumbers = extractLabelsNumbers(mols);

const molecules: Molecule[] = [];
for (const mol of mols) {
const molecule = OCLMolecule.fromMolfile(mol.molfile);
Expand All @@ -11,6 +13,7 @@ export function fromJSON(mols: any[] = []) {
molecules.push(
initMolecule({
molfile: fragment.toMolfileV3(),
label: mol.label ? mol.label : `P${getLabelNumber(reservedNumbers)}`,
}),
);
}
Expand All @@ -20,6 +23,8 @@ export function fromJSON(mols: any[] = []) {
}

export function addMolfile(molecules, molfile) {
const reservedNumbers = extractLabelsNumbers(molecules);

// try to parse molfile
// this will throw if the molecule can not be parsed !
const molecule = OCLMolecule.fromMolfile(molfile);
Expand All @@ -28,13 +33,20 @@ export function addMolfile(molecules, molfile) {
molecules.push(
initMolecule({
molfile: fragment.toMolfileV3(),
label: `P${getLabelNumber(reservedNumbers)}`,
}),
);
}
// we will split if we have many fragments
}

export function setMolfile(molecules, molfile, key) {
export function setMolfile(
molecules,
currentMolecule: Pick<Molecule, 'key' | 'molfile' | 'label' | 'isFloat'>,
) {
const { molfile, key, label, isFloat } = currentMolecule;
const reservedNumbers = extractLabelsNumbers(molecules);

// try to parse molfile
// this will throw if the molecule can not be parsed !
let molecule = OCLMolecule.fromMolfile(molfile);
Expand All @@ -47,19 +59,48 @@ export function setMolfile(molecules, molfile, key) {
molecules.push(
initMolecule({
molfile: fragment.toMolfileV3(),
label: `P${getLabelNumber(reservedNumbers)}`,
}),
);
}
} else if (fragments.length === 1) {
const fragment = fragments[0];
const _mol = initMolecule({
isFloat: true,
isFloat,
molfile: fragment.toMolfileV3(),
key: key,
key,
label,
});
let molIndex = molecules.findIndex((m) => m.key === key);
molecules.splice(molIndex, 1, _mol);
}

// we will split if we have many fragments
}

export function extractNumber(value: string) {
return /(?<number>\d+)/.exec(value)?.groups?.number || null;
}

export function extractLabelsNumbers(molecules: Molecule[]) {
const values: number[] = [];
for (const molecule of molecules) {
const value = extractNumber(molecule.label);
if (value) {
values.push(Number(value));
}
}
return values;
}

export function getLabelNumber(reserveNumbers: number[]): number {
for (let i = 1; i < Number.MAX_VALUE; i++) {
if (reserveNumbers.includes(i)) {
continue;
}

return i;
}

return 1;
}

0 comments on commit 907d09a

Please sign in to comment.