diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5d359075a..773c93a62 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,19 +5,19 @@
"version": "0.2.0",
"configurations": [
{
- "type": "chrome",
- "request": "launch",
- "name": "Launch Chrome against localhost",
- "url": "http://localhost:8080",
- "webRoot": "${workspaceFolder}"
- },
- {
- "name": "Attach to Chrome against localhost",
+ "name": "Attach to Chrome",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"urlFilter": "http://localhost:8080/*",
"webRoot": "${workspaceFolder}"
+ },
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
}
]
}
diff --git a/js/components/common/Modal/index.js b/js/components/common/Modal/index.js
index 004fa7d7d..237b00452 100644
--- a/js/components/common/Modal/index.js
+++ b/js/components/common/Modal/index.js
@@ -24,7 +24,18 @@ const useStyles = makeStyles(theme => ({
}));
export const Modal = memo(
- ({ children, open, loading, onClose, noPadding, resizable, onResize, otherClasses, ...rest }) => {
+ ({
+ children,
+ open,
+ loading,
+ onClose,
+ noPadding,
+ resizable,
+ onResize,
+ otherClasses,
+ otherContentClasses,
+ ...rest
+ }) => {
const classes = useStyles();
const content = loading ? : children;
@@ -48,7 +59,13 @@ export const Modal = memo(
{ [otherClasses]: !!otherClasses }
)}
>
-
{content}
+
+ {content}
+
);
diff --git a/js/components/common/Surfaces/Panel/index.js b/js/components/common/Surfaces/Panel/index.js
index 2c819e854..1d204e8b3 100644
--- a/js/components/common/Surfaces/Panel/index.js
+++ b/js/components/common/Surfaces/Panel/index.js
@@ -13,7 +13,8 @@ import ExpandLess from '@material-ui/icons/ExpandLess';
const useStyles = makeStyles(theme => ({
root: {
- backgroundColor: theme.palette.background.paper
+ backgroundColor: theme.palette.background.paper,
+ height: '100%'
},
body: {
padding: theme.spacing(1)
@@ -113,7 +114,7 @@ export const Panel = memo(
{title && (
2 ? 4 : 6) : 12}
+ xs={hasExpansion || headerActions ? (headerActions && headerActions.length > 1 ? 4 : 6) : 12}
className={classes.headerTitle}
>
{withTooltip ? (
@@ -135,7 +136,7 @@ export const Panel = memo(
container
direction="row"
justify="flex-end"
- xs={title ? (headerActions && headerActions.length > 2 ? 8 : 6) : 12}
+ xs={title ? (headerActions && headerActions.length > 1 ? 8 : 6) : 12}
>
{headerActions &&
headerActions.map((action, index) => (
diff --git a/js/components/datasets/crossReferenceDialog.js b/js/components/datasets/crossReferenceDialog.js
index d832ebb5c..78361819d 100644
--- a/js/components/datasets/crossReferenceDialog.js
+++ b/js/components/datasets/crossReferenceDialog.js
@@ -60,7 +60,8 @@ const useStyles = makeStyles(theme => ({
},
content: {
overflowY: 'auto',
- height: 214
+ height: 214,
+ width: 'fit-content'
},
search: {
margin: theme.spacing(1),
@@ -317,21 +318,27 @@ export const CrossReferenceDialog = memo(
{moleculeList.length > 0 &&
- moleculeList.map((data, index, array) => (
-
0 && array[index - 1]}
- nextItemData={index < array?.length && array[index + 1]}
- removeOfAllSelectedTypes={removeOfAllSelectedTypes}
- />
- ))}
+ moleculeList.map((data, index, array) => {
+ let molecule = Object.assign({ isCrossReference: true }, data.molecule);
+ let previousData = index > 0 && Object.assign({ isCrossReference: true }, array[index - 1]);
+ let nextData = index < array?.length && Object.assign({ isCrossReference: true }, array[index + 1]);
+
+ return (
+
+ );
+ })}
{!(moleculeList.length > 0) && (
diff --git a/js/components/datasets/datasetMoleculeView.js b/js/components/datasets/datasetMoleculeView.js
index 74365ec00..3f3588eac 100644
--- a/js/components/datasets/datasetMoleculeView.js
+++ b/js/components/datasets/datasetMoleculeView.js
@@ -37,7 +37,6 @@ import { ArrowDownward, ArrowUpward, MyLocation } from '@material-ui/icons';
import { isNumber, isString } from 'lodash';
import { SvgTooltip } from '../common';
-
const useStyles = makeStyles(theme => ({
container: {
padding: theme.spacing(1) / 4,
@@ -162,6 +161,10 @@ const useStyles = makeStyles(theme => ({
inheritWidth: {
width: 'inherit'
},
+ widthOverflow: {
+ maxWidth: '180px',
+ overflow: 'hidden'
+ },
rank: {
fontStyle: 'italic',
fontSize: 7
@@ -541,9 +544,9 @@ export const DatasetMoleculeView = memo(
onChange={e => {
const result = e.target.checked;
if (result) {
- dispatch(appendMoleculeToCompoundsOfDatasetToBuy(datasetID, currentID));
+ dispatch(appendMoleculeToCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle));
} else {
- dispatch(removeMoleculeFromCompoundsOfDatasetToBuy(datasetID, currentID));
+ dispatch(removeMoleculeFromCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle));
}
}}
/>
@@ -554,7 +557,13 @@ export const DatasetMoleculeView = memo(
{/* Title label */}
-
+
diff --git a/js/components/datasets/inspirationDialog.js b/js/components/datasets/inspirationDialog.js
index 78e61c1c4..007782ab3 100644
--- a/js/components/datasets/inspirationDialog.js
+++ b/js/components/datasets/inspirationDialog.js
@@ -218,29 +218,38 @@ export const InspirationDialog = memo(
surface: removeSurface
};
+ const selectMoleculeSite = moleculeGroupSite => {};
+
const removeOfAllSelectedTypes = () => {
proteinList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
+
dispatch(removeHitProtein(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
complexList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
dispatch(removeComplex(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
ligandList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
dispatch(removeLigand(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
surfaceList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
dispatch(removeSurface(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
densityList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
dispatch(removeDensity(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
vectorOnList?.forEach(moleculeID => {
- const foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ let foundedMolecule = moleculeList?.find(mol => mol.id === moleculeID);
+ foundedMolecule = foundedMolecule && Object.assign({ isInspiration: true }, foundedMolecule);
dispatch(removeVector(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length]));
});
};
@@ -386,19 +395,26 @@ export const InspirationDialog = memo(
{moleculeList.length > 0 &&
- moleculeList.map((molecule, index, array) => (
-
0 && array[index - 1]}
- nextItemData={index < array?.length && array[index + 1]}
- removeOfAllSelectedTypes={removeOfAllSelectedTypes}
- />
- ))}
+ moleculeList.map((molecule, index, array) => {
+ let data = Object.assign({ isInspiration: true }, molecule);
+ let previousData = index > 0 && Object.assign({ isInspiration: true }, array[index - 1]);
+ let nextData = index < array?.length && Object.assign({ isInspiration: true }, array[index + 1]);
+
+ return (
+
+ );
+ })}
{!(moleculeList.length > 0) && (
diff --git a/js/components/datasets/redux/actions.js b/js/components/datasets/redux/actions.js
index c8044aacb..7159713be 100644
--- a/js/components/datasets/redux/actions.js
+++ b/js/components/datasets/redux/actions.js
@@ -278,14 +278,14 @@ export const setFilterWithInspirations = isChecked => ({
payload: isChecked
});
-export const appendMoleculeToCompoundsOfDatasetToBuy = (datasetID, moleculeID) => ({
+export const appendMoleculeToCompoundsOfDatasetToBuy = (datasetID, moleculeID, moleculeTitle) => ({
type: constants.APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET,
- payload: { datasetID, moleculeID }
+ payload: { datasetID, moleculeID, moleculeTitle }
});
-export const removeMoleculeFromCompoundsOfDatasetToBuy = (datasetID, moleculeID) => ({
+export const removeMoleculeFromCompoundsOfDatasetToBuy = (datasetID, moleculeID, moleculeTitle) => ({
type: constants.REMOVE_MOLECULE_FROM_COMPOUNDS_TO_BUY_OF_DATASET,
- payload: { datasetID, moleculeID }
+ payload: { datasetID, moleculeID, moleculeTitle }
});
export const reloadDatasetsReducer = savedDatasetsReducers => {
diff --git a/js/components/datasets/redux/dispatchActions.js b/js/components/datasets/redux/dispatchActions.js
index 166efe7e8..8a5ae26e6 100644
--- a/js/components/datasets/redux/dispatchActions.js
+++ b/js/components/datasets/redux/dispatchActions.js
@@ -28,7 +28,7 @@ import {
} from './actions';
import { base_url } from '../../routes/constants';
import {
- generateMoleculeId,
+ generateMoleculeCompoundId,
generateHitProteinObject,
generateComplexObject,
generateSurfaceObject,
@@ -64,7 +64,7 @@ export const addDatasetHitProtein = (stage, data, colourToggle, datasetID) => di
const currentOrientation = stage.viewerControls.getOrientation();
dispatch(setOrientation(VIEWS.MAJOR_VIEW, currentOrientation));
});
- dispatch(appendProteinList(datasetID, generateMoleculeId(data)));
+ dispatch(appendProteinList(datasetID, generateMoleculeCompoundId(data)));
};
export const removeDatasetHitProtein = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -77,7 +77,7 @@ export const removeDatasetHitProtein = (stage, data, colourToggle, datasetID) =>
stage
)
);
- dispatch(removeFromProteinList(datasetID, generateMoleculeId(data)));
+ dispatch(removeFromProteinList(datasetID, generateMoleculeCompoundId(data)));
};
export const addDatasetComplex = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -94,7 +94,7 @@ export const addDatasetComplex = (stage, data, colourToggle, datasetID) => dispa
const currentOrientation = stage.viewerControls.getOrientation();
dispatch(setOrientation(VIEWS.MAJOR_VIEW, currentOrientation));
});
- dispatch(appendComplexList(datasetID, generateMoleculeId(data)));
+ dispatch(appendComplexList(datasetID, generateMoleculeCompoundId(data)));
};
export const removeDatasetComplex = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -104,7 +104,7 @@ export const removeDatasetComplex = (stage, data, colourToggle, datasetID) => di
stage
)
);
- dispatch(removeFromComplexList(datasetID, generateMoleculeId(data)));
+ dispatch(removeFromComplexList(datasetID, generateMoleculeCompoundId(data)));
};
export const addDatasetSurface = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -121,7 +121,7 @@ export const addDatasetSurface = (stage, data, colourToggle, datasetID) => dispa
const currentOrientation = stage.viewerControls.getOrientation();
dispatch(setOrientation(VIEWS.MAJOR_VIEW, currentOrientation));
});
- dispatch(appendSurfaceList(datasetID, generateMoleculeId(data)));
+ dispatch(appendSurfaceList(datasetID, generateMoleculeCompoundId(data)));
};
export const removeDatasetSurface = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -131,7 +131,7 @@ export const removeDatasetSurface = (stage, data, colourToggle, datasetID) => di
stage
)
);
- dispatch(removeFromSurfaceList(datasetID, generateMoleculeId(data)));
+ dispatch(removeFromSurfaceList(datasetID, generateMoleculeCompoundId(data)));
};
export const addDatasetLigand = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -151,7 +151,7 @@ export const addDatasetLigand = (stage, data, colourToggle, datasetID) => dispat
// keep current orientation of NGL View
stage.viewerControls.orient(currentOrientation);
});
- dispatch(appendLigandList(datasetID, generateMoleculeId(data)));
+ dispatch(appendLigandList(datasetID, generateMoleculeCompoundId(data)));
};
export const removeDatasetLigand = (stage, data, colourToggle, datasetID) => dispatch => {
@@ -161,7 +161,7 @@ export const removeDatasetLigand = (stage, data, colourToggle, datasetID) => dis
stage
)
);
- dispatch(removeFromLigandList(datasetID, generateMoleculeId(data)));
+ dispatch(removeFromLigandList(datasetID, generateMoleculeCompoundId(data)));
};
export const loadDataSets = targetId => dispatch =>
diff --git a/js/components/datasets/redux/selectors.js b/js/components/datasets/redux/selectors.js
index 0b33faa5a..3ceaf3a7e 100644
--- a/js/components/datasets/redux/selectors.js
+++ b/js/components/datasets/redux/selectors.js
@@ -254,20 +254,24 @@ export const getFilteredDatasetMoleculeList = createSelector(
for (let prioAttr of sortedAttributes) {
const order = filterProperties[prioAttr].order;
- const scoreValueOfA = Object.keys(a.numerical_scores).find(key => key === prioAttr) && a.numerical_scores[prioAttr];
- scoreValueOfA = scoreValueOfA || (Object.keys(a.text_scores).find(key => key === prioAttr) && a.text_scores[prioAttr]);
- const scoreValueOfB = Object.keys(b.numerical_scores).find(key => key === prioAttr) && b.numerical_scores[prioAttr];
- scoreValueOfB = scoreValueOfB || (Object.keys(b.text_scores).find(key => key === prioAttr) && b.text_scores[prioAttr]);
-
- if (scoreValueOfA === "Y") {
+ const scoreValueOfA =
+ Object.keys(a.numerical_scores).find(key => key === prioAttr) && a.numerical_scores[prioAttr];
+ scoreValueOfA =
+ scoreValueOfA || (Object.keys(a.text_scores).find(key => key === prioAttr) && a.text_scores[prioAttr]);
+ const scoreValueOfB =
+ Object.keys(b.numerical_scores).find(key => key === prioAttr) && b.numerical_scores[prioAttr];
+ scoreValueOfB =
+ scoreValueOfB || (Object.keys(b.text_scores).find(key => key === prioAttr) && b.text_scores[prioAttr]);
+
+ if (scoreValueOfA === 'Y') {
scoreValueOfA = 1;
- } else if (scoreValueOfA === "N") {
+ } else if (scoreValueOfA === 'N') {
scoreValueOfA = 0;
}
- if (scoreValueOfB === "Y") {
+ if (scoreValueOfB === 'Y') {
scoreValueOfB = 1;
- } else if (scoreValueOfB === "N") {
+ } else if (scoreValueOfB === 'N') {
scoreValueOfB = 0;
}
@@ -314,7 +318,10 @@ export const getCrossReferenceCompoundListByCompoundName = createSelector(
Object.keys(moleculesDatasetMap).forEach(datasetID => {
const currentList = moleculesDatasetMap[datasetID];
if (currentList && Array.isArray(currentList)) {
- results.push({ molecule: currentList.find(item => item.name === compoundName), datasetID });
+ let molecule = currentList.find(item => item.name === compoundName);
+ if (molecule) {
+ results.push({ molecule, datasetID });
+ }
}
});
return results;
diff --git a/js/components/datasets/selectedCompoundsList.js b/js/components/datasets/selectedCompoundsList.js
index ee6979dad..bcc764b97 100644
--- a/js/components/datasets/selectedCompoundsList.js
+++ b/js/components/datasets/selectedCompoundsList.js
@@ -1,6 +1,7 @@
import React, { memo, useContext, useEffect, useRef, useState } from 'react';
import { Panel } from '../common/Surfaces/Panel';
-import { CircularProgress, Grid, makeStyles, Typography } from '@material-ui/core';
+import { CircularProgress, Grid, makeStyles, Typography, Button } from '@material-ui/core';
+import { CloudDownload } from '@material-ui/icons';
import { useDispatch, useSelector } from 'react-redux';
import { getMoleculesObjectIDListOfCompoundsToBuy } from './redux/selectors';
import InfiniteScroll from 'react-infinite-scroller';
@@ -19,6 +20,8 @@ import {
import MoleculeView from '../preview/molecule/moleculeView';
import { NglContext } from '../nglView/nglProvider';
import { VIEWS } from '../../constants/constants';
+import FileSaver from 'file-saver';
+import JSZip from 'jszip';
const useStyles = makeStyles(theme => ({
container: {
@@ -35,6 +38,9 @@ const useStyles = makeStyles(theme => ({
},
notFound: {
paddingTop: theme.spacing(2)
+ },
+ sdfButton: {
+ marginRight: theme.spacing(1)
}
}));
@@ -46,8 +52,7 @@ export const SelectedCompoundList = memo(({ height }) => {
const moleculesPerPage = 5;
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(0);
- const moleculesObjectIDListOfCompoundsToBuy = useSelector(getMoleculesObjectIDListOfCompoundsToBuy);
- const isOpenInspirationDialog = useSelector(state => state.datasetsReducers.isOpenInspirationDialog);
+ const moleculesObjectIDListOfCompoundsToBuy = useSelector(getMoleculesObjectIDListOfCompoundsToBuy); const isOpenInspirationDialog = useSelector(state => state.datasetsReducers.isOpenInspirationDialog);
const isOpenCrossReferenceDialog = useSelector(state => state.datasetsReducers.isOpenCrossReferenceDialog);
const [selectedMoleculeRef, setSelectedMoleculeRef] = useState(null);
const inspirationDialogRef = useRef();
@@ -132,8 +137,56 @@ export const SelectedCompoundList = memo(({ height }) => {
};
}, [dispatch]);
+ const downloadAsCsv = () => {
+ let data = 'smiles,dataset';
+ moleculesObjectIDListOfCompoundsToBuy.forEach(compound => {
+ data += `\n${compound.molecule.smiles},${compound.datasetID}`;
+ });
+ const dataBlob = new Blob([data], {type: 'text/csv;charset=utf-8'});
+
+ FileSaver.saveAs(dataBlob, 'selectedCompounds.csv');
+ };
+
+ const downloadAsSdf = async () => {
+ const zip = new JSZip();
+ const folders = {};
+
+ moleculesObjectIDListOfCompoundsToBuy.forEach(compound => {
+ const datasetID = compound.datasetID;
+ let folder = folders[datasetID];
+ if (!folder) {
+ folder = zip.folder(datasetID);
+ folders[datasetID] = folder;
+ }
+
+ const { name, sdf_info } = compound.molecule;
+ folder.file(`${name}.sdf`, sdf_info);
+ });
+
+ const zipBlob = await zip.generateAsync({ type: 'blob' });
+ FileSaver.saveAs(zipBlob, 'selectedCompounds.zip');
+ };
+
return (
-
+ }
+ >
+ Download CSV
+ ,
+ }
+ >
+ Download SDF
+
+ ]}>
{isOpenInspirationDialog && (
{
currentMolecule.V = true;
break;
default:
- currentMolecule = { name: part, L: true, P: false, C: false, S: false, V: false };
- molecules.push(currentMolecule);
+ if (part.toLowerCase() === URL_TOKENS.exact) {
+ currentMolecule.exact = true;
+ } else {
+ currentMolecule = { name: part, L: true, P: false, C: false, S: false, V: false, exact: false };
+ molecules.push(currentMolecule);
+ }
break;
}
} else {
- currentMolecule = { name: part, L: true, P: false, C: false, S: false, V: false };
+ currentMolecule = { name: part, L: true, P: false, C: false, S: false, V: false, exact: false };
molecules.push(currentMolecule);
}
} else {
diff --git a/js/components/header/index.js b/js/components/header/index.js
index 1b4af97b5..30ea8e58b 100644
--- a/js/components/header/index.js
+++ b/js/components/header/index.js
@@ -26,7 +26,8 @@ import {
SupervisorAccount,
Menu as MenuIcon,
Work,
- Description
+ Description,
+ Timeline
} from '@material-ui/icons';
import { HeaderContext } from './headerContext';
import { Button } from '../common';
@@ -39,6 +40,7 @@ import { useHistory } from 'react-router-dom';
import { IssueReport } from '../userFeedback/issueReport';
import { IdeaReport } from '../userFeedback/ideaReport';
import { FundersModal } from '../funders/fundersModal';
+import { TrackingModal } from '../tracking/trackingModal';
// eslint-disable-next-line import/extensions
import { version } from '../../../package.json';
@@ -94,6 +96,7 @@ export default memo(
const [openMenu, setOpenMenu] = useState(false);
const [openFunders, setOpenFunders] = useState(false);
+ const [openTrackingModal, setOpenTrackingModal] = useState(false);
const openXchem = () => {
// window.location.href = 'https://www.diamond.ac.uk/Instruments/Mx/Fragment-Screening.html';
@@ -211,6 +214,16 @@ export default memo(
+
+ }
+ variant="text"
+ size="small"
+ onClick={() => setOpenTrackingModal(true)}
+ >
+ Timeline
+
+
@@ -275,6 +288,7 @@ export default memo(
)}
setOpenFunders(false)} />
+ setOpenTrackingModal(false)} />
{
};
export const generateMoleculeId = data => ({
- id: data.id
+ id: data.id,
+ name: data.protein_code,
+ isInspiration: data.isInspiration
+});
+
+export const generateMoleculeCompoundId = data => ({
+ id: data.id,
+ name: data.name,
+ isCrossReference: data.isCrossReference
});
export const getVectorWithColorByCountOfCompounds = (item, currentVectorCompounds) => {
diff --git a/js/components/preview/molecule/moleculeView.js b/js/components/preview/molecule/moleculeView.js
index 891a1818c..e6e3391aa 100644
--- a/js/components/preview/molecule/moleculeView.js
+++ b/js/components/preview/molecule/moleculeView.js
@@ -32,7 +32,6 @@ import { moleculeProperty } from './helperConstants';
import { centerOnLigandByMoleculeID } from '../../../reducers/ngl/dispatchActions';
import { SvgTooltip } from '../../common';
-
const useStyles = makeStyles(theme => ({
container: {
padding: theme.spacing(1) / 4,
@@ -346,7 +345,9 @@ const MoleculeView = memo(
isLigandOn || isProteinOn || isComplexOn || isSurfaceOn || isVectorOn ? selected_style : not_selected_style;
const addNewLigand = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addLigand(stage, data, colourToggle));
};
@@ -377,7 +378,9 @@ const MoleculeView = memo(
};
const addNewProtein = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addHitProtein(stage, data, colourToggle));
};
@@ -403,7 +406,9 @@ const MoleculeView = memo(
};
const addNewComplex = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addComplex(stage, data, colourToggle));
};
@@ -428,7 +433,9 @@ const MoleculeView = memo(
};
const addNewSurface = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addSurface(stage, data, colourToggle));
};
@@ -445,7 +452,9 @@ const MoleculeView = memo(
};
const addNewDensity = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addDensity(stage, data, colourToggle));
};
@@ -462,7 +471,9 @@ const MoleculeView = memo(
};
const addNewVector = () => {
- selectMoleculeSite(data.site);
+ if (selectMoleculeSite) {
+ selectMoleculeSite(data.site);
+ }
dispatch(addVector(stage, data)).catch(error => {
throw new Error(error);
});
@@ -537,7 +548,7 @@ const MoleculeView = memo(
moveSelectedMolSettings(previousItemData);
};
- let moleculeTitle = data?.protein_code.replace(`${target_on_name}-`, '');
+ let moleculeTitle = data?.protein_code.replace(new RegExp(`${target_on_name}-`, 'i'), '');
return (
<>
diff --git a/js/components/preview/molecule/redux/dispatchActions.js b/js/components/preview/molecule/redux/dispatchActions.js
index 9ace772fd..078eeb080 100644
--- a/js/components/preview/molecule/redux/dispatchActions.js
+++ b/js/components/preview/molecule/redux/dispatchActions.js
@@ -42,8 +42,8 @@ import { setCompoundImage } from '../../summary/redux/actions';
import { noCompoundImage } from '../../summary/redux/reducer';
import { getMoleculeOfCurrentVector } from '../../../../reducers/selection/selectors';
import { resetCurrentCompoundsSettings } from '../../compounds/redux/actions';
-import {selectMoleculeGroup} from '../../moleculeGroups/redux/dispatchActions';
-import {setDirectAccess, setDirectAccessProcessed} from '../../../../reducers/api/actions';
+import { selectMoleculeGroup } from '../../moleculeGroups/redux/dispatchActions';
+import { setDirectAccess, setDirectAccessProcessed } from '../../../../reducers/api/actions';
/**
* Convert the JSON into a list of arrow objects
@@ -390,7 +390,6 @@ export const applyDirectSelection = (stage, stageSummaryView) => (dispatch, getS
const state = getState();
const directDisplay = state.apiReducers.direct_access;
- const mol_group_selection = state.selectionReducers.mol_group_selection;
const fragmentDisplayList = state.selectionReducers.fragmentDisplayList;
const proteinList = state.selectionReducers.proteinList;
const complexList = state.selectionReducers.complexList;
@@ -404,14 +403,19 @@ export const applyDirectSelection = (stage, stageSummaryView) => (dispatch, getS
//const molGroupMap = getMolGroupNameToId();
directDisplay.molecules.forEach(m => {
let keys = Object.keys(allMols);
+ let directProteinNameModded = m.name.toLowerCase();
+ let directProteinCodeModded = `${directDisplay.target.toLowerCase()}-${directProteinNameModded}`;
for (let groupIndex = 0; groupIndex < keys.length; groupIndex++) {
let groupId = keys[groupIndex];
let molList = allMols[groupId];
let molCount = molList.length;
for (let molIndex = 0; molIndex < molCount; molIndex++) {
let mol = molList[molIndex];
- if (mol.protein_code.includes(m.name) || mol.protein_code.includes(m.name.toLowerCase())) {
+ let proteinCodeModded = mol.protein_code.toLowerCase();
+ if (m.exact ? proteinCodeModded === directProteinCodeModded : proteinCodeModded.includes(directProteinNameModded)) {
let molGroupId = groupId;
+ // Has to be declared here because otherwise we read stale value
+ const mol_group_selection = getState().selectionReducers.mol_group_selection;
if (!mol_group_selection.includes(parseInt(molGroupId))) {
let molGroup = mol_group_list.find(g => g.id === parseInt(molGroupId));
dispatch(selectMoleculeGroup(molGroup, stageSummaryView));
@@ -438,5 +442,4 @@ export const applyDirectSelection = (stage, stageSummaryView) => (dispatch, getS
// dispatch(setDirectAccess({}));
dispatch(setDirectAccessProcessed(true));
}
-
};
diff --git a/js/components/preview/viewerControls/displayControls/index.js b/js/components/preview/viewerControls/displayControls/index.js
index e33f11d8b..ef9653586 100644
--- a/js/components/preview/viewerControls/displayControls/index.js
+++ b/js/components/preview/viewerControls/displayControls/index.js
@@ -107,7 +107,7 @@ export default memo(({ open, onClose }) => {
// remove from nglReducer and selectionReducer
dispatch(deleteObject(targetObject, nglView.stage, true));
} else {
- dispatch(removeComponentRepresentation(parentKey, representation.uuid));
+ dispatch(removeComponentRepresentation(parentKey, representation));
}
}
};
@@ -118,7 +118,7 @@ export default memo(({ open, onClose }) => {
const targetObject = objectsInView[parentKey];
const nglView = getNglView(objectsInView[parentKey].display_div);
const comp = nglView.stage.getComponentsByName(parentKey).first;
- comp.eachRepresentation(representation => dispatch(removeComponentRepresentation(parentKey, representation.uuid)));
+ comp.eachRepresentation(representation => dispatch(removeComponentRepresentation(parentKey, representation)));
// remove from nglReducer and selectionReducer
dispatch(deleteObject(targetObject, nglView.stage, true));
diff --git a/js/components/tracking/trackingModal.js b/js/components/tracking/trackingModal.js
new file mode 100644
index 000000000..24d78e6fb
--- /dev/null
+++ b/js/components/tracking/trackingModal.js
@@ -0,0 +1,83 @@
+import React, { memo } from 'react';
+import { useSelector } from 'react-redux';
+import Modal from '../common/Modal';
+import { Grid, makeStyles } from '@material-ui/core';
+import { Timeline, TimelineEvent } from 'react-event-timeline';
+import { Check, Clear } from '@material-ui/icons';
+import palette from '../../theme/palette';
+import { Panel } from '../common';
+
+const useStyles = makeStyles(theme => ({
+ customModal: {
+ width: '70%',
+ height: '90%'
+ },
+ customContentModal: {
+ height: '100%'
+ },
+ timelineEvent: {
+ borderBottom: '1px dashed ' + palette.divider,
+ paddingBottom: '10px'
+ },
+ divContainer: {
+ height: '100%',
+ width: '100%',
+ paddingTop: theme.spacing(1) / 2
+ },
+ divScrollable: {
+ height: '100%',
+ width: '100%',
+ overflow: 'auto'
+ },
+ containerExpanded: { height: '100%' }
+}));
+
+export const TrackingModal = memo(({ openModal, onModalClose }) => {
+ const classes = useStyles();
+ const actionList = useSelector(state => state.trackingReducers.truck_actions_list);
+ const orderedActionList = actionList.sort((a, b) => a.timestamp - b.timestamp);
+
+ if (openModal === undefined) {
+ console.log('undefined openModal');
+ onModalClose();
+ }
+
+ return (
+ onModalClose()}
+ >
+
+
+
+
+
+ {orderedActionList &&
+ orderedActionList.map((data, index) => (
+
+ ) : (
+
+ )
+ }
+ iconColor={palette.primary.main}
+ className={classes.timelineEvent}
+ >
+ ))}
+
+
+
+
+
+
+ );
+});
diff --git a/js/index.js b/js/index.js
index a9250104e..fb8ddd49e 100644
--- a/js/index.js
+++ b/js/index.js
@@ -11,7 +11,9 @@ import { applyMiddleware, createStore } from 'redux';
import { rootReducer } from './reducers/rootReducer';
import { saveStore } from './components/helpers/globalStore';
import thunkMiddleware from 'redux-thunk';
+import trackingMiddleware from './reducers/tracking/trackingMiddleware';
import { composeWithDevTools } from 'redux-devtools-extension';
+
require('react-hot-loader/patch');
if (process.env.NODE_ENV === 'production') {
@@ -37,7 +39,8 @@ if (process.env.NODE_ENV === 'production') {
const middlewareEnhancer = applyMiddleware(
//loggerMiddleware,
- thunkMiddleware
+ thunkMiddleware,
+ trackingMiddleware
);
const enhancers = [middlewareEnhancer];
const composedEnhancers = composeWithDevTools(...enhancers);
diff --git a/js/reducers/api/selectors.js b/js/reducers/api/selectors.js
index 409289146..3bca464a3 100644
--- a/js/reducers/api/selectors.js
+++ b/js/reducers/api/selectors.js
@@ -1 +1,2 @@
export const getMoleculeList = state => state.apiReducers.molecule_list;
+export const getAllMoleculeList = state => state.apiReducers.all_mol_lists;
diff --git a/js/reducers/ngl/actions.js b/js/reducers/ngl/actions.js
index 3ab83d5d5..000f43a5c 100644
--- a/js/reducers/ngl/actions.js
+++ b/js/reducers/ngl/actions.js
@@ -23,9 +23,9 @@ export const addComponentRepresentation = (objectInViewID, newRepresentation) =>
objectInViewID
});
-export const removeComponentRepresentation = (objectInViewID, representationID) => ({
+export const removeComponentRepresentation = (objectInViewID, representation) => ({
type: CONSTANTS.REMOVE_COMPONENT_REPRESENTATION,
- representationID,
+ representation,
objectInViewID
});
diff --git a/js/reducers/ngl/nglReducers.js b/js/reducers/ngl/nglReducers.js
index 009f1981d..a187a370b 100644
--- a/js/reducers/ngl/nglReducers.js
+++ b/js/reducers/ngl/nglReducers.js
@@ -80,12 +80,12 @@ export default function nglReducers(state = INITIAL_STATE, action = {}) {
});
case CONSTANTS.REMOVE_COMPONENT_REPRESENTATION:
+ const representationID = action.representation && action.representation.uuid;
const newObjInViewWithRemovedRepresentation = JSON.parse(JSON.stringify(state.objectsInView));
if (newObjInViewWithRemovedRepresentation[action.objectInViewID].representations) {
for (let i = 0; i < newObjInViewWithRemovedRepresentation[action.objectInViewID].representations.length; i++) {
if (
- newObjInViewWithRemovedRepresentation[action.objectInViewID].representations[i].uuid ===
- action.representationID
+ newObjInViewWithRemovedRepresentation[action.objectInViewID].representations[i].uuid === representationID
) {
newObjInViewWithRemovedRepresentation[action.objectInViewID].representations.splice(i, 1);
break;
diff --git a/js/reducers/rootReducer.js b/js/reducers/rootReducer.js
index 92c06c584..17933c666 100644
--- a/js/reducers/rootReducer.js
+++ b/js/reducers/rootReducer.js
@@ -11,6 +11,7 @@ import { previewReducers } from '../components/preview/redux';
import { projectReducers } from '../components/projects/redux/reducer';
import { issueReducers } from '../components/userFeedback/redux/reducer';
import { datasetsReducers } from '../components/datasets/redux/reducer';
+import trackingReducers from './tracking/trackingReducers';
const rootReducer = combineReducers({
apiReducers,
@@ -21,7 +22,8 @@ const rootReducer = combineReducers({
previewReducers,
projectReducers,
issueReducers,
- datasetsReducers
+ datasetsReducers,
+ trackingReducers
});
export { rootReducer };
diff --git a/js/reducers/selection/selectors.js b/js/reducers/selection/selectors.js
index f54f9b288..8aa98b9d5 100644
--- a/js/reducers/selection/selectors.js
+++ b/js/reducers/selection/selectors.js
@@ -1,5 +1,5 @@
import { createSelector } from 'reselect';
-import { getMoleculeList } from '../api/selectors';
+import { getAllMoleculeList } from '../api/selectors';
const getCurrentCompoundClass = state => state.previewReducers.compounds.currentCompoundClass;
const getVectorList = state => state.selectionReducers.vector_list;
@@ -10,14 +10,16 @@ const getCompoundsOfVectors = state => state.selectionReducers.compoundsOfVector
export const getMoleculeOfCurrentVector = createSelector(
getCurrentVector,
getVectorList,
- getMoleculeList,
+ getAllMoleculeList,
(selectedVectorSmile, vectorList, moleculeList) => {
if (selectedVectorSmile !== null && vectorList && moleculeList) {
const foundedVector = vectorList.find(vector => vector.name.includes(selectedVectorSmile));
if (foundedVector) {
- for (const molecule in moleculeList) {
- if (moleculeList.hasOwnProperty(molecule)) {
- if (molecule.id === foundedVector.moleculeId) {
+ for (const moleculeProperty in moleculeList) {
+ if (moleculeList.hasOwnProperty(moleculeProperty)) {
+ let molecules = moleculeList[moleculeProperty];
+ let molecule = molecules.find(m => m.id === foundedVector.moleculeId);
+ if (molecule) {
return molecule;
}
}
diff --git a/js/reducers/tracking/actions.js b/js/reducers/tracking/actions.js
new file mode 100644
index 000000000..decfb1e3a
--- /dev/null
+++ b/js/reducers/tracking/actions.js
@@ -0,0 +1,22 @@
+import { constants } from './constants';
+
+export const setActionsList = function(truck_actions_list) {
+ return {
+ type: constants.SET_ACTIONS_LIST,
+ truck_actions_list: truck_actions_list
+ };
+};
+
+export const appendToActionList = function(truck_action) {
+ return {
+ type: constants.APPEND_ACTIONS_LIST,
+ truck_action: truck_action
+ };
+};
+
+export const setCurrentActionsList = function(current_actions_list) {
+ return {
+ type: constants.SET_CURRENT_ACTIONS_LIST,
+ current_actions_list: current_actions_list
+ };
+};
diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js
new file mode 100644
index 000000000..18b77abef
--- /dev/null
+++ b/js/reducers/tracking/constants.js
@@ -0,0 +1,62 @@
+const prefix = 'REDUCERS_TRACKING_';
+
+export const constants = {
+ SET_ACTIONS_LIST: prefix + 'SET_ACTIONS_LIST',
+ APPEND_ACTIONS_LIST: prefix + 'APPEND_ACTIONS_LIST',
+ SET_CURRENT_ACTIONS_LIST: prefix + 'SET_CURRENT_ACTIONS_LIST'
+};
+
+export const actionType = {
+ TARGET_LOADED: 'TARGET_LOADED',
+ SITE_TURNED_ON: 'SITE_TURNED_ON',
+ SITE_TURNED_OFF: 'SITE_TURNED_OFF',
+ LIGAND_TURNED_ON: 'LIGAND_TURNED_ON',
+ LIGAND_TURNED_OFF: 'LIGAND_TURNED_OFF',
+ SIDECHAINS_TURNED_ON: 'SIDECHAINS_TURNED_ON',
+ SIDECHAINS_TURNED_OFF: 'SIDECHAINS_TURNED_OFF',
+ INTERACTIONS_TURNED_ON: 'INTERACTIONS_TURNED_ON',
+ INTERACTIONS_TURNED_OFF: 'INTERACTIONS_TURNED_OFF',
+ SURFACE_TURNED_ON: 'SURFACE_TURNED_ON',
+ SURFACE_TURNED_OFF: 'SURFACE_TURNED_OFF',
+ VECTORS_TURNED_ON: 'VECTORS_TURNED_ON',
+ VECTORS_TURNED_OFF: 'VECTORS_TURNED_OFF',
+ VECTOR_SELECTED: 'VECTOR_SELECTED',
+ VECTOR_DESELECTED: 'VECTOR_DESELECTED',
+ MOLECULE_ADDED_TO_SHOPPING_CART: 'MOLECULE_ADDED_TO_SHOPPING_CART',
+ MOLECULE_REMOVED_FROM_SHOPPING_CART: 'MOLECULE_REMOVED_FROM_SHOPPING_CART',
+ COMPOUND_SELECTED: 'COMPOUND_SELECTED',
+ COMPOUND_DESELECTED: 'COMPOUND_DESELECTED',
+ REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED',
+ REPRESENTATION_ADDED: 'REPRESENTATION_ADDED',
+ REPRESENTATION_REMOVED: 'REPRESENTATION_REMOVED'
+};
+
+export const actionDescription = {
+ LOADED: 'was loaded',
+ TURNED_ON: 'was turned on',
+ TURNED_OFF: 'was turned off',
+ SELECTED: 'was selected',
+ DESELECTED: 'was deselected',
+ ADDED: 'was added',
+ REMOVED: 'was removed',
+ CHANGED: 'was changed',
+ TO_SHOPPING_CART: 'to shopping cart',
+ FROM_SHOPPING_CART: 'from shopping cart',
+ LIGAND: 'Ligand',
+ SIDECHAINS: 'Sidechain',
+ INTERACTIONS: 'Interaction',
+ VECTOR: 'Vector',
+ SURFACE: 'Surface',
+ SITE: 'Site',
+ TARGET: 'Target'
+};
+
+export const actionObjectType = {
+ TARGET: 'TARGET',
+ SITE: 'SITE',
+ MOLECULE: 'MOLECULE',
+ COMPOUND: 'COMPOUND',
+ INSPIRATION: 'INSPIRATION',
+ CROSS_REFERENCE: 'CROSS_REFERENCE',
+ REPRESENTATION: 'REPRESENTATION'
+};
diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js
new file mode 100644
index 000000000..ae62942f7
--- /dev/null
+++ b/js/reducers/tracking/dispatchActions.js
@@ -0,0 +1,185 @@
+import { setCurrentActionsList } from './actions';
+import { actionType } from './constants';
+import { VIEWS } from '../../../js/constants/constants';
+
+export const selectCurrentActionsList = () => (dispatch, getState) => {
+ const state = getState();
+
+ const actionList = state.trackingReducers.truck_actions_list;
+ const currentTargetOn = state.apiReducers.target_on;
+ const currentSites = state.selectionReducers.mol_group_selection;
+ const currentLigands = state.selectionReducers.fragmentDisplayList;
+ const currentProteins = state.selectionReducers.proteinList;
+ const currentComplexes = state.selectionReducers.complexLists;
+ const currentSurfaces = state.selectionReducers.surfaceList;
+ const currentVectors = state.selectionReducers.vectorOnList;
+ const currentBuyList = state.selectionReducers.to_buy_list;
+ const currentVector = state.selectionReducers.currentVector;
+
+ const currentDatasetLigands = state.datasetsReducers.ligandLists;
+ const currentDatasetProteins = state.datasetsReducers.proteinList;
+ const currentDatasetComplexes = state.datasetsReducers.complexLists;
+ const currentDatasetSurfaces = state.datasetsReducers.surfaceLists;
+
+ const currentDatasetBuyList = state.datasetsReducers.compoundsToBuyDatasetMap;
+ const currentobjectsInView = state.nglReducers.objectsInView;
+
+ const orderedActionList = actionList.reverse((a, b) => a.timestamp - b.timestamp);
+ const currentTargets = (currentTargetOn && [currentTargetOn]) || [];
+ const currentVectorSmiles = (currentVector && [currentVector]) || [];
+
+ let currentActions = [];
+
+ getCurrentActionList(orderedActionList, actionType.TARGET_LOADED, getCollection(currentTargets), currentActions);
+ getCurrentActionList(orderedActionList, actionType.SITE_TURNED_ON, getCollection(currentSites), currentActions);
+ getCurrentActionList(orderedActionList, actionType.LIGAND_TURNED_ON, getCollection(currentLigands), currentActions);
+ getCurrentActionList(
+ orderedActionList,
+ actionType.SIDECHAINS_TURNED_ON,
+ getCollection(currentProteins),
+ currentActions
+ );
+ getCurrentActionList(
+ orderedActionList,
+ actionType.INTERACTIONS_TURNED_ON,
+ getCollection(currentComplexes),
+ currentActions
+ );
+ getCurrentActionList(orderedActionList, actionType.SURFACE_TURNED_ON, getCollection(currentSurfaces), currentActions);
+ getCurrentActionList(orderedActionList, actionType.VECTORS_TURNED_ON, getCollection(currentVectors), currentActions);
+ getCurrentActionList(
+ orderedActionList,
+ actionType.VECTOR_SELECTED,
+ getCollection(currentVectorSmiles),
+ currentActions
+ );
+
+ getCurrentActionList(
+ orderedActionList,
+ actionType.MOLECULE_ADDED_TO_SHOPPING_CART,
+ getCollectionOfShoppingCart(currentBuyList),
+ currentActions
+ );
+
+ getCurrentActionList(
+ orderedActionList,
+ actionType.LIGAND_TURNED_ON,
+ getCollectionOfDataset(currentDatasetLigands),
+ currentActions
+ );
+ getCurrentActionList(
+ orderedActionList,
+ actionType.SIDECHAINS_TURNED_ON,
+ getCollectionOfDataset(currentDatasetProteins),
+ currentActions
+ );
+ getCurrentActionList(
+ orderedActionList,
+ actionType.INTERACTIONS_TURNED_ON,
+ getCollectionOfDataset(currentDatasetComplexes),
+
+ currentActions
+ );
+ getCurrentActionList(
+ orderedActionList,
+ actionType.SURFACE_TURNED_ON,
+ getCollectionOfDataset(currentDatasetSurfaces),
+ currentActions
+ );
+
+ getCurrentActionList(
+ orderedActionList,
+ actionType.COMPOUND_SELECTED,
+ getCollectionOfDataset(currentDatasetBuyList),
+ currentActions
+ );
+
+ getCurrentActionList(
+ orderedActionList,
+ actionType.REPRESENTATION_CHANGED,
+ getCollectionOfDatasetOfRepresentation(currentobjectsInView),
+ currentActions
+ );
+
+ dispatch(setCurrentActionsList(currentActions));
+};
+
+const getCurrentActionList = (orderedActionList, type, collection, currentActions) => {
+ let actionList =
+ type !== actionType.REPRESENTATION_CHANGED
+ ? orderedActionList.filter(action => action.type === type)
+ : orderedActionList.filter(
+ action =>
+ action.type === actionType.REPRESENTATION_ADDED ||
+ action.type === actionType.REPRESENTATION_REMOVED ||
+ action.type === actionType.REPRESENTATION_CHANGED
+ );
+ if (collection) {
+ collection.forEach(data => {
+ let action = actionList.find(action => action.object_id === data.id && action.dataset_id === data.datasetId);
+
+ if (action) {
+ currentActions.push(Object.assign(mapCurrentAction(action)));
+ }
+ });
+ }
+};
+
+const mapCurrentAction = action => {
+ return Object.assign({
+ timestamp: action.timestamp,
+ object_name: action.object_name,
+ object_type: action.object_type,
+ action_type: action.type
+ });
+};
+
+const getCollection = dataList => {
+ let list = [];
+ if (dataList) {
+ var result = dataList.map(value => ({ id: value }));
+ list.push(...result);
+ }
+ return list;
+};
+
+const getCollectionOfDataset = dataList => {
+ let list = [];
+ if (dataList) {
+ for (const datasetId in dataList) {
+ let values = dataList[datasetId];
+ if (values) {
+ var result = values.map(value => ({ id: value, datasetId: datasetId }));
+ list.push(...result);
+ }
+ }
+ }
+ return list;
+};
+
+const getCollectionOfShoppingCart = dataList => {
+ let list = [];
+ if (dataList) {
+ dataList.forEach(data => {
+ let value = data.vector;
+ if (value) {
+ list.push({ id: value });
+ }
+ });
+ }
+ return list;
+};
+
+const getCollectionOfDatasetOfRepresentation = dataList => {
+ let list = [];
+ for (const view in dataList) {
+ let objectView = dataList[view];
+ if (objectView && objectView !== null && objectView.display_div === VIEWS.MAJOR_VIEW) {
+ let value = dataList[view].name;
+ if (value) {
+ list.push({ id: value });
+ }
+ }
+ }
+ return list;
+};
diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js
new file mode 100644
index 000000000..edb1a3313
--- /dev/null
+++ b/js/reducers/tracking/trackingActions.js
@@ -0,0 +1,495 @@
+import { actionType, actionObjectType, actionDescription } from './constants';
+import { constants as apiConstants } from '../api/constants';
+import { CONSTANTS as nglConstants } from '../ngl/constants';
+import { constants as selectionConstants } from '../selection/constants';
+import { constants as customDatasetConstants } from '../../components/datasets/redux/constants';
+import { DJANGO_CONTEXT } from '../../utils/djangoContext';
+
+export const findTruckAction = (action, state) => {
+ const username = DJANGO_CONTEXT['username'];
+ let truckAction = null;
+ if (action.type.includes(apiConstants.SET_TARGET_ON)) {
+ if (action.target_on) {
+ let targetName = getTargetName(action.target_on, state);
+ truckAction = {
+ type: actionType.TARGET_LOADED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.TARGET,
+ object_name: targetName,
+ object_id: action.target_on,
+ text: `${actionDescription.TARGET} ${targetName} ${actionDescription.LOADED}`
+ };
+ }
+ } else if (action.type.includes(apiConstants.SET_MOL_GROUP_ON)) {
+ if (action.mol_group_on) {
+ let molGroupSelection = state.selectionReducers.mol_group_selection;
+ let currentMolGroup = molGroupSelection && molGroupSelection.find(o => o === action.mol_group_on);
+ if (!currentMolGroup) {
+ let molGroupName = getMolGroupName(action.mol_group_on, state);
+ truckAction = {
+ type: actionType.SITE_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.SITE,
+ object_name: molGroupName,
+ object_id: action.mol_group_on,
+ text: `${actionDescription.SITE} ${molGroupName} ${actionDescription.TURNED_ON}`
+ };
+ }
+ }
+ } else if (action.type.includes(selectionConstants.SET_OBJECT_SELECTION)) {
+ let objectId = action.payload && action.payload[0];
+ if (objectId) {
+ let molGroupName = getMolGroupName(objectId, state);
+ truckAction = {
+ type: actionType.SITE_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.SITE,
+ object_name: molGroupName,
+ object_id: objectId,
+ text: `${actionDescription.SITE} ${molGroupName} ${actionDescription.TURNED_OFF}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_FRAGMENT_DISPLAY_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.LIGAND_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.LIGAND} ${actionDescription.TURNED_ON} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_FRAGMENT_DISPLAY_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.LIGAND_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.LIGAND} ${actionDescription.TURNED_OFF} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_PROTEIN_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.SIDECHAINS_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.SIDECHAINS} ${actionDescription.TURNED_ON} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_PROTEIN_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.SIDECHAINS_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.SIDECHAINS} ${actionDescription.TURNED_OFF} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_COMPLEX_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.INTERACTIONS_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.INTERACTIONS} ${actionDescription.TURNED_ON} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_COMPLEX_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.INTERACTIONS_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.INTERACTIONS} ${actionDescription.TURNED_OFF} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_SURFACE_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.SURFACE_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.SURFACE} ${actionDescription.TURNED_ON} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_SURFACE_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.SURFACE_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.SURFACE} ${actionDescription.TURNED_OFF} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_VECTOR_ON_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.VECTORS_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.VECTOR} ${actionDescription.TURNED_ON} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_VECTOR_ON_LIST)) {
+ if (action.item) {
+ let objectType = action.item.isInspiration === true ? actionObjectType.INSPIRATION : actionObjectType.MOLECULE;
+ let objectName = action.item.name || getMoleculeName(action.item.id, state);
+
+ truckAction = {
+ type: actionType.VECTORS_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.item.id,
+ text: `${actionDescription.VECTOR} ${actionDescription.TURNED_OFF} ${objectType} ${objectName}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.APPEND_TO_BUY_LIST)) {
+ if (action.item) {
+ let objectType = actionObjectType.MOLECULE;
+ let objectName = action.vector;
+
+ truckAction = {
+ type: actionType.MOLECULE_ADDED_TO_SHOPPING_CART,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.MOLECULE,
+ object_name: objectName,
+ object_id: objectName,
+ text: `${objectType} ${objectName} ${actionDescription.ADDED} ${actionDescription.TO_SHOPPING_CART}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.REMOVE_FROM_TO_BUY_LIST)) {
+ if (action.item) {
+ let objectType = actionObjectType.MOLECULE;
+ let objectName = action.vector;
+
+ truckAction = {
+ type: actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: objectName,
+ text: `${objectType} ${objectName} ${actionDescription.REMOVED} ${actionDescription.FROM_SHOPPING_CART}`
+ };
+ }
+ } else if (action.type.includes(selectionConstants.SET_CURRENT_VECTOR)) {
+ if (action.payload) {
+ let objectType = actionObjectType.MOLECULE;
+ let objectName = action.payload;
+
+ truckAction = {
+ type: actionType.VECTOR_SELECTED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload,
+ text: `${actionDescription.VECTOR} ${objectName} ${actionDescription.SELECTED}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET)) {
+ if (action.payload) {
+ let objectType = actionObjectType.COMPOUND;
+ let objectName = action.payload.moleculeTitle;
+
+ truckAction = {
+ type: actionType.COMPOUND_SELECTED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.moleculeID,
+ dataset_id: action.payload.datasetID,
+ text: `${objectType} ${objectName} ${actionDescription.SELECTED} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.REMOVE_MOLECULE_FROM_COMPOUNDS_TO_BUY_OF_DATASET)) {
+ if (action.payload) {
+ let objectType = actionObjectType.COMPOUND;
+ let objectName = action.payload.moleculeTitle;
+
+ truckAction = {
+ type: actionType.COMPOUND_DESELECTED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.moleculeID,
+ dataset_id: action.payload.datasetID,
+ text: `${objectType} ${objectName} ${actionDescription.DESELECTED} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.APPEND_LIGAND_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.LIGAND_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.LIGAND} ${actionDescription.TURNED_ON} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.REMOVE_FROM_LIGAND_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.LIGAND_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.LIGAND} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.APPEND_PROTEIN_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.SIDECHAINS_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.SIDECHAINS} ${actionDescription.TURNED_ON} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.REMOVE_FROM_PROTEIN_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.SIDECHAINS_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.SIDECHAINS} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.APPEND_COMPLEX_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.INTERACTIONS_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.INTERACTIONS} ${actionDescription.TURNED_ON} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.REMOVE_FROM_COMPLEX_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.INTERACTIONS_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.INTERACTIONS} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.APPEND_SURFACE_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.SURFACE_TURNED_ON,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.SURFACE} ${actionDescription.TURNED_ON} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(customDatasetConstants.REMOVE_FROM_SURFACE_LIST)) {
+ if (action.payload && action.payload.item) {
+ let objectType =
+ action.payload.item.isCrossReference === true ? actionObjectType.CROSS_REFERENCE : actionObjectType.COMPOUND;
+ let objectName = action.payload.item.name;
+
+ truckAction = {
+ type: actionType.SURFACE_TURNED_OFF,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: objectName,
+ object_id: action.payload.item.id,
+ dataset_id: action.payload.datasetID,
+ text: `${actionDescription.SURFACE} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}`
+ };
+ }
+ } else if (action.type.includes(nglConstants.UPDATE_COMPONENT_REPRESENTATION)) {
+ let objectType = actionObjectType.REPRESENTATION;
+
+ truckAction = {
+ type: actionType.REPRESENTATION_CHANGED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.REPRESENTATION,
+ object_name: action.objectInViewID,
+ object_id: action.objectInViewID,
+ representation_id: action.representationID,
+ new_representation: action.newRepresentation,
+ text: `${objectType} parameter of ${action.objectInViewID} ${actionDescription.CHANGED}`
+ };
+ } else if (action.type.includes(nglConstants.ADD_COMPONENT_REPRESENTATION)) {
+ let objectType = actionObjectType.REPRESENTATION;
+ let representationName = action.newRepresentation && action.newRepresentation.type;
+
+ truckAction = {
+ type: actionType.REPRESENTATION_ADDED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: actionObjectType.REPRESENTATION,
+ object_name: representationName,
+ object_id: action.objectInViewID,
+ new_representation: action.newRepresentation,
+ text: `${objectType} ${representationName} of ${action.objectInViewID} ${actionDescription.ADDED}`
+ };
+ } else if (action.type.includes(nglConstants.REMOVE_COMPONENT_REPRESENTATION)) {
+ let objectType = actionObjectType.REPRESENTATION;
+ let representationName = action.representation && action.representation.type;
+
+ truckAction = {
+ type: actionType.REPRESENTATION_REMOVED,
+ timestamp: Date.now(),
+ username: username,
+ object_type: objectType,
+ object_name: representationName,
+ object_id: action.objectInViewID,
+ representation_id: action.representationID,
+ text: `${objectType} ${representationName} of ${action.objectInViewID} ${actionDescription.REMOVED}`
+ };
+ }
+
+ return truckAction;
+};
+
+const getMolGroupName = (molGroupId, state) => {
+ let molGroupList = state.apiReducers.mol_group_list;
+ let molGroup = molGroupList.find(group => group.id === molGroupId);
+ let molGroupName = (molGroup && molGroup.description) || '';
+ return molGroupName;
+};
+
+const getTargetName = (targetId, state) => {
+ let targetList = state.apiReducers.target_id_list;
+ let target = targetList.find(target => target.id === targetId);
+ let targetName = (target && target.title) || '';
+ return targetName;
+};
+
+const getMoleculeName = (moleculeId, state) => {
+ let moleculeList = state.apiReducers.all_mol_lists;
+ let moleculeName = '';
+
+ if (moleculeList) {
+ for (const group in moleculeList) {
+ let molecules = moleculeList[group];
+
+ let molecule = molecules.find(molecule => molecule.id === moleculeId);
+ if (molecule && molecule != null) {
+ moleculeName = molecule.protein_code;
+ break;
+ }
+ }
+ }
+ return moleculeName;
+};
diff --git a/js/reducers/tracking/trackingMiddleware.js b/js/reducers/tracking/trackingMiddleware.js
new file mode 100644
index 000000000..f284a210b
--- /dev/null
+++ b/js/reducers/tracking/trackingMiddleware.js
@@ -0,0 +1,19 @@
+import { appendToActionList } from './actions';
+import { constants } from './constants';
+import { findTruckAction } from './trackingActions';
+
+const trackingMiddleware = ({ dispatch, getState }) => next => action => {
+ //console.log(`Redux Log:`, action);
+
+ const state = getState();
+ if (!action.type.includes(constants.APPEND_ACTIONS_LIST)) {
+ let truckAction = findTruckAction(action, state);
+ if (truckAction && truckAction != null) {
+ dispatch(appendToActionList(truckAction));
+ }
+ }
+
+ next(action);
+};
+
+export default trackingMiddleware;
diff --git a/js/reducers/tracking/trackingReducers.js b/js/reducers/tracking/trackingReducers.js
new file mode 100644
index 000000000..a9e9e793b
--- /dev/null
+++ b/js/reducers/tracking/trackingReducers.js
@@ -0,0 +1,28 @@
+import { constants } from './constants';
+
+export const INITIAL_STATE = {
+ truck_actions_list: [],
+ current_actions_list: []
+};
+
+export default function trackingReducers(state = INITIAL_STATE, action = {}) {
+ switch (action.type) {
+ case constants.SET_ACTIONS_LIST:
+ return Object.assign({}, state, {
+ truck_actions_list: action.truck_actions_list
+ });
+
+ case constants.APPEND_ACTIONS_LIST:
+ return Object.assign({}, state, {
+ truck_actions_list: [...new Set([...state.truck_actions_list, action.truck_action])]
+ });
+
+ case constants.SET_CURRENT_ACTIONS_LIST:
+ return Object.assign({}, state, {
+ current_actions_list: action.current_actions_list
+ });
+
+ default:
+ return state;
+ }
+}
diff --git a/package.json b/package.json
index f44f91aaf..f45476057 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "fragalysis-frontend",
- "version": "0.9.11",
+ "version": "0.9.29",
"description": "Frontend for fragalysis",
"main": "webpack.config.js",
"scripts": {
@@ -60,6 +60,7 @@
"react-canvas-draw": "^1.1.0",
"react-color": "^2.17.3",
"react-dom": "^16.12.0",
+ "react-event-timeline": "^1.6.3",
"react-hot-loader": "^4.12.18",
"react-infinite-scroller": "^1.2.4",
"react-redux": "^7.1.3",