diff --git a/.gitignore b/.gitignore index 04bc33b34..8e5e32451 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ npm-debug.log *.offsets yarn-error.log /yarn.lock +/debug.log diff --git a/js/components/datasets/crossReferenceDialog.js b/js/components/datasets/crossReferenceDialog.js index d6dc9ec95..9f719cc88 100644 --- a/js/components/datasets/crossReferenceDialog.js +++ b/js/components/datasets/crossReferenceDialog.js @@ -16,7 +16,7 @@ import { } from './redux/dispatchActions'; import { Button } from '../common/Inputs/Button'; import classNames from 'classnames'; -import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; import { colourList, DatasetMoleculeView } from './datasetMoleculeView'; import { NglContext } from '../nglView/nglProvider'; import { VIEWS } from '../../constants/constants'; @@ -132,7 +132,7 @@ export const CrossReferenceDialog = memo( const { getNglView } = useContext(NglContext); const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const moleculeList = useSelector(state => getCrossReferenceCompoundListByCompoundName(state)); const isLoadingCrossReferenceScores = useSelector(state => state.datasetsReducers.isLoadingCrossReferenceScores); @@ -272,7 +272,7 @@ export const CrossReferenceDialog = memo( onClick={() => dispatch(handleAllLigandsOfCrossReferenceDialog(isLigandOn, moleculeList, stage)) } - disabled={disableUserInteraction} + disabled={false} > L @@ -289,7 +289,7 @@ export const CrossReferenceDialog = memo( onClick={() => dispatch(removeOrAddAllHitProteinsOfList(isProteinOn, moleculeList, stage)) } - disabled={disableUserInteraction} + disabled={false} > P @@ -305,7 +305,7 @@ export const CrossReferenceDialog = memo( [classes.contColButtonHalfSelected]: isComplexOn === null })} onClick={() => dispatch(removeOrAddAllComplexesOfList(isComplexOn, moleculeList, stage))} - disabled={disableUserInteraction} + disabled={false} > C @@ -336,6 +336,11 @@ export const CrossReferenceDialog = memo( previousItemData={previousData} nextItemData={nextData} removeOfAllSelectedTypes={removeOfAllSelectedTypes} + L={ligandList.includes(data.id)} + P={proteinList.includes(data.id)} + C={complexList.includes(data.id)} + S={false} + V={false} /> ); })} diff --git a/js/components/datasets/datasetMoleculeList.js b/js/components/datasets/datasetMoleculeList.js index 828b1b2bd..ed8c5b157 100644 --- a/js/components/datasets/datasetMoleculeList.js +++ b/js/components/datasets/datasetMoleculeList.js @@ -14,7 +14,7 @@ import { IconButton, ButtonGroup } from '@material-ui/core'; -import React, { useState, useEffect, memo, useRef, useContext, useCallback } from 'react'; +import React, { useState, useEffect, memo, useRef, useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { DatasetMoleculeView, colourList } from './datasetMoleculeView'; import InfiniteScroll from 'react-infinite-scroller'; @@ -23,7 +23,7 @@ import { Panel } from '../common/Surfaces/Panel'; import { ComputeSize } from '../../utils/computeSize'; import { VIEWS } from '../../constants/constants'; import { NglContext } from '../nglView/nglProvider'; -import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; import classNames from 'classnames'; import { addDatasetLigand, @@ -34,7 +34,9 @@ import { removeDatasetComplex, addDatasetSurface, removeDatasetSurface, - autoHideDatasetDialogsOnScroll + autoHideDatasetDialogsOnScroll, + moveMoleculeInspirationsSettings, + removeAllSelectedDatasetMolecules } from './redux/dispatchActions'; import { setFilterDialogOpen, setSearchStringOfCompoundSet } from './redux/actions'; import { DatasetFilter } from './datasetFilter'; @@ -44,6 +46,8 @@ import { debounce } from 'lodash'; import { InspirationDialog } from './inspirationDialog'; import { CrossReferenceDialog } from './crossReferenceDialog'; import { AlertModal } from '../common/Modal/AlertModal'; +import { hideAllSelectedMolecules } from '../preview/molecule/redux/dispatchActions'; +import { getMoleculeList } from '../preview/molecule/redux/selectors'; import { setSelectedAllByType, setDeselectedAllByType } from './redux/actions'; const useStyles = makeStyles(theme => ({ @@ -226,7 +230,10 @@ export const DatasetMoleculeList = memo( const filterRef = useRef(); let joinedMoleculeLists = moleculeLists[datasetID] || []; - const disableUserInteraction = useDisableUserInteraction(); + const getJoinedMoleculeList = useSelector(state => getMoleculeList(state)); + const inspirationMoleculeDataList = useSelector(state => state.datasetsReducers.allInspirationMoleculeDataList); + + // const disableUserInteraction = useDisableUserInteraction(); // TODO Reset Infinity scroll @@ -258,6 +265,15 @@ export const DatasetMoleculeList = memo( const selectedAll = useRef(false); + const objectsInView = useSelector(state => state.nglReducers.objectsInView) || {}; + + const proteinListMolecule = useSelector(state => state.selectionReducers.proteinList); + const complexListMolecule = useSelector(state => state.selectionReducers.complexList); + const fragmentDisplayListMolecule = useSelector(state => state.selectionReducers.fragmentDisplayList); + const surfaceListMolecule = useSelector(state => state.selectionReducers.surfaceList); + const densityListMolecule = useSelector(state => state.selectionReducers.densityList); + const vectorOnListMolecule = useSelector(state => state.selectionReducers.vectorOnList); + const ligandList = useSelector(state => state.datasetsReducers.ligandLists[datasetID]); const proteinList = useSelector(state => state.datasetsReducers.proteinLists[datasetID]); const complexList = useSelector(state => state.datasetsReducers.complexLists[datasetID]); @@ -280,31 +296,30 @@ export const DatasetMoleculeList = memo( surface: removeDatasetSurface }; + const removeOfAllSelectedTypesOfInspirations = () => { + let molecules = [...getJoinedMoleculeList, ...inspirationMoleculeDataList]; + dispatch(hideAllSelectedMolecules(stage, [...molecules])); + }; + const removeOfAllSelectedTypes = () => { - ligandList?.forEach(moleculeID => { - const foundedMolecule = joinedMoleculeLists?.find(mol => mol.id === moleculeID); - dispatch( - removeDatasetLigand(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) - ); - }); - proteinList?.forEach(moleculeID => { - const foundedMolecule = joinedMoleculeLists?.find(mol => mol.id === moleculeID); - dispatch( - removeDatasetHitProtein(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) - ); - }); - complexList?.forEach(moleculeID => { - const foundedMolecule = joinedMoleculeLists?.find(mol => mol.id === moleculeID); - dispatch( - removeDatasetComplex(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) - ); - }); - surfaceList?.forEach(moleculeID => { - const foundedMolecule = joinedMoleculeLists?.find(mol => mol.id === moleculeID); - dispatch( - removeDatasetSurface(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) - ); - }); + dispatch(removeAllSelectedDatasetMolecules(stage)); + }; + + const moveSelectedMoleculeInspirationsSettings = (data, newItemData) => (dispatch, getState) => { + dispatch( + moveMoleculeInspirationsSettings( + data, + newItemData, + stage, + objectsInView, + fragmentDisplayListMolecule, + proteinListMolecule, + complexListMolecule, + surfaceListMolecule, + densityListMolecule, + vectorOnListMolecule + ) + ); }; // TODO "currentMolecules" do not need to correspondent to selections in {type}List @@ -552,7 +567,7 @@ export const DatasetMoleculeList = memo( [classes.contColButtonSelected]: isLigandOn })} onClick={() => onButtonToggle('ligand')} - disabled={disableUserInteraction} + disabled={false} > L @@ -566,7 +581,7 @@ export const DatasetMoleculeList = memo( [classes.contColButtonSelected]: isProteinOn })} onClick={() => onButtonToggle('protein')} - disabled={disableUserInteraction} + disabled={false} > P @@ -581,7 +596,7 @@ export const DatasetMoleculeList = memo( [classes.contColButtonSelected]: isComplexOn })} onClick={() => onButtonToggle('complex')} - disabled={disableUserInteraction} + disabled={false} > C @@ -646,6 +661,13 @@ export const DatasetMoleculeList = memo( previousItemData={index > 0 && array[index - 1]} nextItemData={index < array?.length && array[index + 1]} removeOfAllSelectedTypes={removeOfAllSelectedTypes} + removeOfAllSelectedTypesOfInspirations={removeOfAllSelectedTypesOfInspirations} + moveSelectedMoleculeInspirationsSettings={moveSelectedMoleculeInspirationsSettings} + L={ligandList.includes(data.id)} + P={proteinList.includes(data.id)} + C={complexList.includes(data.id)} + S={surfaceList.includes(data.id)} + V={false} /> ))} diff --git a/js/components/datasets/datasetMoleculeView.js b/js/components/datasets/datasetMoleculeView.js index d834c70d1..43aa58a57 100644 --- a/js/components/datasets/datasetMoleculeView.js +++ b/js/components/datasets/datasetMoleculeView.js @@ -10,7 +10,7 @@ import SVGInline from 'react-svg-inline'; import classNames from 'classnames'; import { VIEWS } from '../../constants/constants'; import { NglContext } from '../nglView/nglProvider'; -import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; import { addDatasetLigand, removeDatasetLigand, @@ -23,6 +23,7 @@ import { clickOnInspirations, getDatasetMoleculeID } from './redux/dispatchActions'; + import { base_url } from '../routes/constants'; import { api } from '../../utils/api'; import { isAnyInspirationTurnedOn, getFilteredDatasetMoleculeList } from './redux/selectors'; @@ -31,13 +32,19 @@ import { removeMoleculeFromCompoundsOfDatasetToBuy, setCrossReferenceCompoundName, setIsOpenCrossReferenceDialog, + setInspirationFragmentList, + setInspirationMoleculeDataList, setSelectedAll, setDeselectedAll } from './redux/actions'; import { centerOnLigandByMoleculeID } from '../../reducers/ngl/dispatchActions'; import { ArrowDownward, ArrowUpward, MyLocation } from '@material-ui/icons'; -import { isNumber, isString } from 'lodash'; +import { isString } from 'lodash'; import { SvgTooltip } from '../common'; +import { OBJECT_TYPE } from '../nglView/constants'; +import { getRepresentationsByType } from '../nglView/generatingObjects'; +import { getMolImage } from '../preview/molecule/redux/dispatchActions'; +import { MOL_TYPE } from '../preview/molecule/redux/constants'; const useStyles = makeStyles(theme => ({ container: { @@ -252,36 +259,38 @@ export const DatasetMoleculeView = memo( index, previousItemData, nextItemData, - removeOfAllSelectedTypes + removeOfAllSelectedTypes, + removeOfAllSelectedTypesOfInspirations, + moveSelectedMoleculeInspirationsSettings, + L, P, C, S, V }) => { const selectedAll = useRef(false); const currentID = (data && data.id) || undefined; const classes = useStyles(); const ref = useRef(null); - const dispatch = useDispatch(); const compoundsToBuyList = useSelector(state => state.datasetsReducers.compoundsToBuyDatasetMap[datasetID]); - const ligandList = useSelector(state => state.datasetsReducers.ligandLists[datasetID]); - const proteinList = useSelector(state => state.datasetsReducers.proteinLists[datasetID]); - const complexList = useSelector(state => state.datasetsReducers.complexLists[datasetID]); - const surfaceList = useSelector(state => state.datasetsReducers.surfaceLists[datasetID]); + const allInspirations = useSelector(state => state.datasetsReducers.allInspirations); + const datasets = useSelector(state => state.datasetsReducers.datasets); const filteredScoreProperties = useSelector(state => state.datasetsReducers.filteredScoreProperties); const filter = useSelector(state => state.selectionReducers.filter); const isAnyInspirationOn = useSelector(state => isAnyInspirationTurnedOn(state, (data && data.computed_inspirations) || []) ); + const filteredDatasetMoleculeList = useSelector(state => getFilteredDatasetMoleculeList(state, datasetID)); + const objectsInView = useSelector(state => state.nglReducers.objectsInView) || {}; const [image, setImage] = useState(img_data_init); const { getNglView } = useContext(NglContext); const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; - const isLigandOn = (currentID && ligandList.includes(currentID)) || false; - const isProteinOn = (currentID && proteinList.includes(currentID)) || false; - const isComplexOn = (currentID && complexList.includes(currentID)) || false; - const isSurfaceOn = (currentID && surfaceList.includes(currentID)) || false; + const isLigandOn = L; + const isProteinOn = P; + const isComplexOn = C; + const isSurfaceOn = S; const isCheckedToBuy = (currentID && compoundsToBuyList && compoundsToBuyList.includes(currentID)) || false; @@ -290,7 +299,7 @@ export const DatasetMoleculeView = memo( const areArrowsVisible = isLigandOn || isProteinOn || isComplexOn || isSurfaceOn; - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const refOnCancelImage = useRef(); const getRandomColor = () => colourList[currentID % colourList.length]; @@ -307,45 +316,20 @@ export const DatasetMoleculeView = memo( // componentDidMount useEffect(() => { - if (/*refOnCancelImage.current === undefined && */ data && data.smiles) { - let onCancel = () => {}; - let url = new URL(`${base_url}/viewer/img_from_smiles/`); - const params = { - width: imageHeight, - height: imageWidth, - smiles: data.smiles - }; - Object.keys(params).forEach(key => url.searchParams.append(key, params[key])); - - api({ - url, - cancel: onCancel - }) - .then(response => { - if (response.data !== undefined) { - setImage(response.data); - } - }) - .catch(error => { - throw new Error(error); - }); - refOnCancelImage.current = onCancel; - } - return () => { - if (refOnCancelImage) { - refOnCancelImage.current(); - } - }; + dispatch(getMolImage(data.smiles, MOL_TYPE.DATASET, imageHeight, imageWidth)).then(i => { + setImage(i); + }); }, [ - complexList, + C, currentID, data, - ligandList, + L, imageHeight, imageWidth, data.smiles, data.id, - filteredDatasetMoleculeList + filteredDatasetMoleculeList, + dispatch ]); const svg_image = ( @@ -505,35 +489,84 @@ export const DatasetMoleculeView = memo( const moveSelectedMoleculeSettings = (newItemData, datasetIdOfMolecule) => { if (newItemData) { if (isLigandOn) { - dispatch(addDatasetLigand(stage, newItemData, colourToggle, datasetIdOfMolecule)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.LIGAND, datasetID); + dispatch(addDatasetLigand(stage, newItemData, colourToggle, datasetIdOfMolecule, representations)); } if (isProteinOn) { - dispatch(addDatasetHitProtein(stage, newItemData, colourToggle, datasetIdOfMolecule)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.PROTEIN, datasetID); + dispatch(addDatasetHitProtein(stage, newItemData, colourToggle, datasetIdOfMolecule, representations)); } if (isComplexOn) { - dispatch(addDatasetComplex(stage, newItemData, colourToggle, datasetIdOfMolecule)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.COMPLEX, datasetID); + dispatch(addDatasetComplex(stage, newItemData, colourToggle, datasetIdOfMolecule, representations)); } if (isSurfaceOn) { - dispatch(addDatasetSurface(stage, newItemData, colourToggle, datasetIdOfMolecule)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.SURFACE, datasetID); + dispatch(addDatasetSurface(stage, newItemData, colourToggle, datasetIdOfMolecule, representations)); } } }; + const scrollToElement = element => { + element.scrollIntoView({ + behavior: 'auto', + block: 'nearest', + inline: 'nearest' + }); + }; + + const getInspirationsForMol = (datasetId, molId) => { + let inspirations = []; + + if (allInspirations && allInspirations.hasOwnProperty(datasetId) && allInspirations[datasetId].hasOwnProperty(molId)) { + inspirations = allInspirations[datasetId][molId]; + } + + return inspirations; + }; + const handleClickOnDownArrow = () => { + const refNext = ref.current.nextSibling; + scrollToElement(refNext); + removeOfAllSelectedTypes(); + removeOfAllSelectedTypesOfInspirations(); + const nextItem = (nextItemData.hasOwnProperty('molecule') && nextItemData.molecule) || nextItemData; const nextDatasetID = (nextItemData.hasOwnProperty('datasetID') && nextItemData.datasetID) || datasetID; + const moleculeTitleNext = nextItem && nextItem.name; + + const inspirations = getInspirationsForMol(datasetID, nextItem.id); + dispatch(setInspirationMoleculeDataList(inspirations)); moveSelectedMoleculeSettings(nextItem, nextDatasetID); + dispatch(moveSelectedMoleculeInspirationsSettings(data, nextItem)); + dispatch(setCrossReferenceCompoundName(moleculeTitleNext)); + if (setRef && ref.current) { + setRef(refNext); + } }; const handleClickOnUpArrow = () => { + const refPrevious = ref.current.previousSibling; + scrollToElement(refPrevious); + removeOfAllSelectedTypes(); + removeOfAllSelectedTypesOfInspirations(); + const previousItem = (previousItemData.hasOwnProperty('molecule') && previousItemData.molecule) || previousItemData; const previousDatasetID = (previousItemData.hasOwnProperty('datasetID') && previousItemData.datasetID) || datasetID; + const moleculeTitlePrev = previousItem && previousItem.name; + const inspirations = getInspirationsForMol(datasetID, previousItem.id); + dispatch(setInspirationMoleculeDataList(inspirations)); moveSelectedMoleculeSettings(previousItem, previousDatasetID); + dispatch(moveSelectedMoleculeInspirationsSettings(data, previousItem)); + dispatch(setCrossReferenceCompoundName(moleculeTitlePrev)); + if (setRef && ref.current) { + setRef(refPrevious); + } }; const moleculeTitle = data && data.name; @@ -618,7 +651,7 @@ export const DatasetMoleculeView = memo( onClick={() => { dispatch(centerOnLigandByMoleculeID(stage, getDatasetMoleculeID(datasetID, currentID))); }} - disabled={disableUserInteraction || !isLigandOn} + disabled={false || !isLigandOn} > @@ -646,7 +679,7 @@ export const DatasetMoleculeView = memo( onProtein(true); onComplex(true); }} - disabled={disableUserInteraction} + disabled={false} > A @@ -660,7 +693,7 @@ export const DatasetMoleculeView = memo( [classes.contColButtonSelected]: isLigandOn })} onClick={() => onLigand()} - disabled={disableUserInteraction} + disabled={false} > L @@ -674,7 +707,7 @@ export const DatasetMoleculeView = memo( [classes.contColButtonSelected]: isProteinOn })} onClick={() => onProtein()} - disabled={disableUserInteraction} + disabled={false} > P @@ -689,7 +722,7 @@ export const DatasetMoleculeView = memo( [classes.contColButtonSelected]: isComplexOn })} onClick={() => onComplex()} - disabled={disableUserInteraction} + disabled={false} > C @@ -703,7 +736,7 @@ export const DatasetMoleculeView = memo( [classes.contColButtonSelected]: isSurfaceOn })} onClick={() => onSurface()} - disabled={disableUserInteraction} + disabled={false} > S @@ -722,14 +755,14 @@ export const DatasetMoleculeView = memo( clickOnInspirations({ datasetID, currentID, - computed_inspirations: data && data.computed_inspirations + computed_inspirations: getInspirationsForMol(datasetID, currentID) }) ); if (setRef) { setRef(ref.current); } }} - disabled={disableUserInteraction} + disabled={false} > F @@ -751,7 +784,7 @@ export const DatasetMoleculeView = memo( setRef(ref.current); } }} - disabled={disableUserInteraction} + disabled={false} > X @@ -812,7 +845,7 @@ export const DatasetMoleculeView = memo( @@ -822,7 +855,7 @@ export const DatasetMoleculeView = memo( diff --git a/js/components/datasets/inspirationDialog.js b/js/components/datasets/inspirationDialog.js index 3ad1f5de5..f124f75c3 100644 --- a/js/components/datasets/inspirationDialog.js +++ b/js/components/datasets/inspirationDialog.js @@ -24,14 +24,13 @@ import { removeDensity, removeVector } from '../preview/molecule/redux/dispatchActions'; -import { loadInspirationMoleculesDataList } from './redux/dispatchActions'; import MoleculeView from '../preview/molecule/moleculeView'; import { moleculeProperty } from '../preview/molecule/helperConstants'; import { debounce } from 'lodash'; -import { setInspirationMoleculeDataList, setIsOpenInspirationDialog } from './redux/actions'; +import { setIsOpenInspirationDialog } from './redux/actions'; import { Button } from '../common/Inputs/Button'; import classNames from 'classnames'; -import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; import { colourList } from './datasetMoleculeView'; import { NglContext } from '../nglView/nglProvider'; import { VIEWS } from '../../constants/constants'; @@ -143,8 +142,6 @@ export const InspirationDialog = memo( const { getNglView } = useContext(NglContext); const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; - const inspirationFragmentList = useSelector(state => state.datasetsReducers.inspirationFragmentList); - const isLoadingInspirationListOfMolecules = useSelector( state => state.datasetsReducers.isLoadingInspirationListOfMolecules ); @@ -158,17 +155,7 @@ export const InspirationDialog = memo( const vectorOnList = useSelector(state => state.selectionReducers.vectorOnList); const dispatch = useDispatch(); - const disableUserInteraction = useDisableUserInteraction(); - - useEffect(() => { - if (inspirationFragmentList && inspirationFragmentList.length > 0) { - dispatch(loadInspirationMoleculesDataList(inspirationFragmentList)).catch(error => { - throw new Error(error); - }); - } else { - dispatch(setInspirationMoleculeDataList([])); - } - }, [dispatch, inspirationFragmentList]); + // const disableUserInteraction = useDisableUserInteraction(); let debouncedFn; @@ -393,7 +380,7 @@ export const InspirationDialog = memo( [classes.contColButtonHalfSelected]: isLigandOn === null })} onClick={() => onButtonToggle('ligand')} - disabled={disableUserInteraction} + disabled={false} > L @@ -408,7 +395,7 @@ export const InspirationDialog = memo( [classes.contColButtonHalfSelected]: isProteinOn === null })} onClick={() => onButtonToggle('protein')} - disabled={disableUserInteraction} + disabled={false} > P @@ -424,7 +411,7 @@ export const InspirationDialog = memo( [classes.contColButtonHalfSelected]: isComplexOn === null })} onClick={() => onButtonToggle('complex')} - disabled={disableUserInteraction} + disabled={false} > C @@ -454,6 +441,11 @@ export const InspirationDialog = memo( nextItemData={nextData} removeOfAllSelectedTypes={removeOfAllSelectedTypes} selectMoleculeSite={selectMoleculeSite} + L={ligandList.includes(molecule.id)} + P={proteinList.includes(molecule.id)} + C={complexList.includes(molecule.id)} + S={surfaceList.includes(molecule.id)} + V={vectorOnList.includes(molecule.id)} /> ); })} diff --git a/js/components/datasets/redux/actions.js b/js/components/datasets/redux/actions.js index 75d7f144a..53cbcbe7e 100644 --- a/js/components/datasets/redux/actions.js +++ b/js/components/datasets/redux/actions.js @@ -26,6 +26,11 @@ export const setMoleculeListIsLoading = isLoading => ({ payload: isLoading }); +export const replaceAllMoleculeLists = allMoleculeLists => ({ + type: constants.REPLACE_ALL_MOLECULELISTS, + payload: allMoleculeLists +}); + export const setFilterSettings = (datasetID, filter) => ({ type: constants.SET_FILTER_SETTINGS, payload: { datasetID, filter } @@ -249,11 +254,26 @@ export const setInspirationMoleculeDataList = (moleculeList = []) => ({ payload: moleculeList }); +export const setAllInspirations = (allInspirationsMap) => ({ + type: constants.SET_ALL_INSPIRATIONS, + payload: allInspirationsMap +}); + +export const setAllInspirationMoleculeDataList = (moleculeList = []) => ({ + type: constants.SET_ALL_INSPIRATION_MOLECULE_DATA_LIST, + payload: moleculeList +}); + export const appendToInspirationMoleculeDataList = molecule => ({ type: constants.APPEND_TO_INSPIRATION_MOLECULE_DATA_LIST, payload: molecule }); +export const appendToAllInspirationMoleculeDataList = molecule => ({ + type: constants.APPEND_TO_ALL_INSPIRATION_MOLECULE_DATA_LIST, + payload: molecule +}); + export const removeFromInspirationMoleculeDataList = moleculeID => ({ type: constants.REMOVE_FROM_INSPIRATION_MOLECULE_DATA_LIST, payload: moleculeID diff --git a/js/components/datasets/redux/constants.js b/js/components/datasets/redux/constants.js index 9d47c6ad3..e1d95a045 100644 --- a/js/components/datasets/redux/constants.js +++ b/js/components/datasets/redux/constants.js @@ -3,6 +3,7 @@ const prefix = 'CUSTOM_DATASETS_'; export const constants = { ADD_DATASET: prefix + 'ADD_DATASET', SET_DATASET: prefix + 'SET_DATASET', + REPLACE_ALL_MOLECULELISTS: prefix + 'REPLACE_ALL_MOLECULELISTS', ADD_MOLECULELIST: prefix + 'ADD_MOLECULELIST', REMOVE_MOLECULELIST: prefix + 'REMOVE_MOLECULELIST', SET_IS_LOADING_MOLECULE_LIST: prefix + 'SET_IS_LOADING_MOLECULE_LIST', @@ -28,6 +29,7 @@ export const constants = { SET_INSPIRATION_LIST: prefix + 'SET_INSPIRATION_LIST', APPEND_INSPIRATION_LIST: prefix + 'APPEND_INSPIRATION_LIST', REMOVE_FROM_INSPIRATION_LIST: prefix + 'REMOVE_FROM_INSPIRATION_LIST', + SET_ALL_INSPIRATIONS: prefix + 'SET_ALL_INSPIRATIONS', APPEND_TO_SCORE_DATASET_MAP: prefix + 'APPEND_TO_SCORE_DATASET_MAP', REMOVE_FROM_SCORE_DATASET_MAP: prefix + 'REMOVE_FROM_SCORE_DATASET_MAP', @@ -52,7 +54,9 @@ export const constants = { SET_INSPIRATION_FRAGMENT_LIST: prefix + 'SET_INSPIRATION_FRAGMENT_LIST', REMOVE_FROM_INSPIRATION_FRAGMENT_LIST: prefix + 'REMOVE_FROM_INSPIRATION_FRAGMENT_LIST', SET_INSPIRATION_MOLECULE_DATA_LIST: prefix + 'SET_INSPIRATION_MOLECULE_DATA_LIST', + SET_ALL_INSPIRATION_MOLECULE_DATA_LIST: prefix + 'SET_ALL_INSPIRATION_MOLECULE_DATA_LIST', APPEND_TO_INSPIRATION_MOLECULE_DATA_LIST: prefix + 'APPEND_TO_INSPIRATION_MOLECULE_DATA_LIST', + APPEND_TO_ALL_INSPIRATION_MOLECULE_DATA_LIST: prefix + 'APPEND_TO_ALL_INSPIRATION_MOLECULE_DATA_LIST', REMOVE_FROM_INSPIRATION_MOLECULE_DATA_LIST: prefix + 'REMOVE_FROM_INSPIRATION_MOLECULE_DATA_LIST', APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET: prefix + 'APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET', diff --git a/js/components/datasets/redux/dispatchActions.js b/js/components/datasets/redux/dispatchActions.js index 25c6830f8..9a0956056 100644 --- a/js/components/datasets/redux/dispatchActions.js +++ b/js/components/datasets/redux/dispatchActions.js @@ -16,7 +16,9 @@ import { setFilterProperties, setIsLoadingInspirationListOfMolecules, appendToInspirationMoleculeDataList, + appendToAllInspirationMoleculeDataList, setInspirationMoleculeDataList, + setAllInspirationMoleculeDataList, setInspirationList, setIsOpenInspirationDialog, clearScoreCompoundMap, @@ -41,6 +43,17 @@ import { getInitialDatasetFilterProperties, getInitialDatasetFilterSettings } fr import { COUNT_OF_VISIBLE_SCORES } from './constants'; import { colourList } from '../../preview/molecule/moleculeView'; import { appendMoleculeOrientation } from '../../../reducers/ngl/actions'; +import { isAnyInspirationTurnedOnByType } from './selectors'; +import { + addVector, + addHitProtein, + addComplex, + addSurface, + addLigand, + addDensity +} from '../../preview/molecule/redux/dispatchActions'; +import { OBJECT_TYPE } from '../../nglView/constants'; +import { getRepresentationsByType } from '../../nglView/generatingObjects'; import { setSelectedAllByType, setDeselectedAllByType } from './actions'; export const initializeDatasetFilter = datasetID => (dispatch, getState) => { @@ -51,7 +64,7 @@ export const initializeDatasetFilter = datasetID => (dispatch, getState) => { dispatch(setFilterProperties(datasetID, initFilterProperties)); }; -export const addDatasetHitProtein = (stage, data, colourToggle, datasetID, skipTracking = false) => dispatch => { +export const addDatasetHitProtein = (stage, data, colourToggle, datasetID, skipTracking = false, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign( @@ -59,6 +72,7 @@ export const addDatasetHitProtein = (stage, data, colourToggle, datasetID, skipT generateHitProteinObject(data, colourToggle, base_url, datasetID) ), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -81,7 +95,7 @@ export const removeDatasetHitProtein = (stage, data, colourToggle, datasetID, sk dispatch(removeFromProteinList(datasetID, generateMoleculeCompoundId(data), skipTracking)); }; -export const addDatasetComplex = (stage, data, colourToggle, datasetID, skipTracking = false) => dispatch => { +export const addDatasetComplex = (stage, data, colourToggle, datasetID, skipTracking = false, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign( @@ -89,6 +103,7 @@ export const addDatasetComplex = (stage, data, colourToggle, datasetID, skipTrac generateComplexObject(data, colourToggle, base_url, datasetID) ), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -108,7 +123,7 @@ export const removeDatasetComplex = (stage, data, colourToggle, datasetID, skipT dispatch(removeFromComplexList(datasetID, generateMoleculeCompoundId(data), skipTracking)); }; -export const addDatasetSurface = (stage, data, colourToggle, datasetID) => dispatch => { +export const addDatasetSurface = (stage, data, colourToggle, datasetID, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign( @@ -116,6 +131,7 @@ export const addDatasetSurface = (stage, data, colourToggle, datasetID) => dispa generateSurfaceObject(data, colourToggle, base_url, datasetID) ), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -135,12 +151,13 @@ export const removeDatasetSurface = (stage, data, colourToggle, datasetID) => di dispatch(removeFromSurfaceList(datasetID, generateMoleculeCompoundId(data))); }; -export const addDatasetLigand = (stage, data, colourToggle, datasetID, skipTracking = false) => dispatch => { +export const addDatasetLigand = (stage, data, colourToggle, datasetID, skipTracking = false, representations = undefined) => dispatch => { const currentOrientation = stage.viewerControls.getOrientation(); dispatch( loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateMoleculeObject(data, colourToggle, datasetID)), stage, + previousRepresentations: representations, markAsRightSideLigand: true }) ).finally(() => { @@ -336,6 +353,7 @@ export const loadInspirationMoleculesDataList = (inspirationList = []) => (dispa arrayOfInspirationListSet.map(moleculeID => api({ url: `${base_url}/api/molecules/${moleculeID}/` }).then(response => { dispatch(appendToInspirationMoleculeDataList(response.data)); + dispatch(appendToAllInspirationMoleculeDataList(response.data)); }) ) ).finally(() => { @@ -345,6 +363,11 @@ export const loadInspirationMoleculesDataList = (inspirationList = []) => (dispa return Promise.resolve(); }; +export const clearAllInspirationsOfDataset = () => dispatch => { + // clear inspirations + dispatch(setAllInspirationMoleculeDataList([])); +}; + export const clearInspirationsOfDataset = datasetID => dispatch => { // clear inspirations dispatch(setInspirationList(datasetID, [])); @@ -364,7 +387,7 @@ export const clearDatasetSettings = datasetID => dispatch => { export const clickOnInspirations = ({ datasetID, currentID, computed_inspirations = [] }) => dispatch => { dispatch(setInspirationList(datasetID, [currentID])); - dispatch(setInspirationFragmentList(computed_inspirations)); + dispatch(setInspirationMoleculeDataList(computed_inspirations)); dispatch(setIsOpenInspirationDialog(true)); }; @@ -584,4 +607,136 @@ export const autoHideDatasetDialogsOnScroll = ({ inspirationDialogRef, crossRefe } }; +export const removeAllSelectedDatasetMolecules = stage => (dispatch, getState) => { + const state = getState(); + const datasets = state.datasetsReducers.datasets; + const currentMolecules = state.datasetsReducers.moleculeLists; + if (datasets) { + datasets.forEach(dataset => { + let datasetID = dataset.id; + const ligandList = state.datasetsReducers.ligandLists[datasetID]; + const proteinList = state.datasetsReducers.proteinLists[datasetID]; + const complexList = state.datasetsReducers.complexLists[datasetID]; + const surfaceList = state.datasetsReducers.surfaceLists[datasetID]; + + let molecules = currentMolecules[datasetID]; + ligandList?.forEach(moleculeID => { + const foundedMolecule = molecules?.find(mol => mol.id === moleculeID); + dispatch( + removeDatasetLigand(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) + ); + }); + proteinList?.forEach(moleculeID => { + const foundedMolecule = molecules?.find(mol => mol.id === moleculeID); + dispatch( + removeDatasetHitProtein(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) + ); + }); + complexList?.forEach(moleculeID => { + const foundedMolecule = molecules?.find(mol => mol.id === moleculeID); + dispatch( + removeDatasetComplex(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) + ); + }); + surfaceList?.forEach(moleculeID => { + const foundedMolecule = molecules?.find(mol => mol.id === moleculeID); + dispatch( + removeDatasetSurface(stage, foundedMolecule, colourList[foundedMolecule.id % colourList.length], datasetID) + ); + }); + }); + } +}; + +export const moveMoleculeInspirationsSettings = ( + data, + newItemData, + stage, + objectsInView, + fragmentDisplayListMolecule, + proteinListMolecule, + complexListMolecule, + surfaceListMolecule, + densityListMolecule, + vectorOnListMolecule +) => (dispatch, getState) => { + dispatch(clearAllInspirationsOfDataset()); + if (newItemData) { + let computed_inspirations = (data && data.computed_inspirations) || []; + let isAnyInspirationLigandOn = isAnyInspirationTurnedOnByType(computed_inspirations, fragmentDisplayListMolecule); + let isAnyInspirationProteinOn = isAnyInspirationTurnedOnByType(computed_inspirations, proteinListMolecule); + let isAnyInspirationComplexOn = isAnyInspirationTurnedOnByType(computed_inspirations, complexListMolecule); + let isAnyInspirationSurfaceOn = isAnyInspirationTurnedOnByType(computed_inspirations, surfaceListMolecule); + let isAnyInspirationDensityOn = isAnyInspirationTurnedOnByType(computed_inspirations, densityListMolecule); + let isAnyInspirationVectorOn = isAnyInspirationTurnedOnByType(computed_inspirations, vectorOnListMolecule); + + if ( + isAnyInspirationLigandOn || + isAnyInspirationProteinOn || + isAnyInspirationComplexOn || + isAnyInspirationSurfaceOn || + isAnyInspirationDensityOn || + isAnyInspirationVectorOn + ) { + // dispatch(loadInspirationMoleculesDataList(newItemData.computed_inspirations)).then(() => { + dispatch( + moveInspirations( + stage, + objectsInView, + isAnyInspirationLigandOn, + isAnyInspirationProteinOn, + isAnyInspirationComplexOn, + isAnyInspirationSurfaceOn, + isAnyInspirationDensityOn, + isAnyInspirationVectorOn + ) + ); + // }); + } + } +}; + +const moveInspirations = ( + stage, + objectsInView, + isAnyInspirationLigandOn, + isAnyInspirationProteinOn, + isAnyInspirationComplexOn, + isAnyInspirationSurfaceOn, + isAnyInspirationDensityOn, + isAnyInspirationVectorOn +) => (dispatch, getState) => { + const state = getState(); + const molecules = state.datasetsReducers.inspirationMoleculeDataList; + if (molecules) { + molecules.forEach(molecule => { + if (molecule) { + if (isAnyInspirationLigandOn) { + let representations = getRepresentationsByType(objectsInView, molecule, OBJECT_TYPE.LIGAND); + dispatch(addLigand(stage, molecule, colourList[molecule.id % colourList.length], false, representations)); + } + if (isAnyInspirationProteinOn) { + let representations = getRepresentationsByType(objectsInView, molecule, OBJECT_TYPE.HIT_PROTEIN); + dispatch(addHitProtein(stage, molecule, colourList[molecule.id % colourList.length], representations)); + } + if (isAnyInspirationComplexOn) { + let representations = getRepresentationsByType(objectsInView, molecule, OBJECT_TYPE.COMPLEX); + dispatch(addComplex(stage, molecule, colourList[molecule.id % colourList.length], representations)); + } + if (isAnyInspirationSurfaceOn) { + let representations = getRepresentationsByType(objectsInView, molecule, OBJECT_TYPE.SURFACE); + dispatch(addSurface(stage, molecule, colourList[molecule.id % colourList.length], representations)); + } + if (isAnyInspirationDensityOn) { + let representations = getRepresentationsByType(objectsInView, molecule, OBJECT_TYPE.DENSITY); + dispatch(addDensity(stage, molecule, colourList[molecule.id % colourList.length], representations)); + } + if (isAnyInspirationVectorOn) { + dispatch(addVector(stage, molecule, colourList[molecule.id % colourList.length])); + } + } + }); + } +}; + export const getDatasetMoleculeID = (datasetID, moleculeID) => `datasetID-${datasetID}_moleculeID-${moleculeID}`; diff --git a/js/components/datasets/redux/reducer.js b/js/components/datasets/redux/reducer.js index 4d04a1b65..379e4298d 100644 --- a/js/components/datasets/redux/reducer.js +++ b/js/components/datasets/redux/reducer.js @@ -32,6 +32,8 @@ export const INITIAL_STATE = { inspirationFragmentList: [], isLoadingInspirationListOfMolecules: false, inspirationMoleculeDataList: [], + allInspirationMoleculeDataList: [], + allInspirations: {}, // cross reference isOpenCrossReferenceDialog: false, @@ -135,6 +137,9 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { case constants.SET_DATASET: return Object.assign({}, state, { datasets: action.payload }); + case constants.REPLACE_ALL_MOLECULELISTS: + return {...state, moleculeLists: action.payload}; + case constants.ADD_MOLECULELIST: // initialize also control containers const initializedState = initializeContainerLists(state, action.payload.datasetID); @@ -295,11 +300,21 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { case constants.SET_INSPIRATION_MOLECULE_DATA_LIST: return Object.assign({}, state, { inspirationMoleculeDataList: action.payload }); + case constants.SET_ALL_INSPIRATION_MOLECULE_DATA_LIST: + return Object.assign({}, state, { allInspirationMoleculeDataList: action.payload }); + case constants.APPEND_TO_INSPIRATION_MOLECULE_DATA_LIST: const extendedInspirationMoleculeDataList = new Set(state.inspirationMoleculeDataList); extendedInspirationMoleculeDataList.add(action.payload); return Object.assign({}, state, { inspirationMoleculeDataList: [...extendedInspirationMoleculeDataList] }); + case constants.APPEND_TO_ALL_INSPIRATION_MOLECULE_DATA_LIST: + const molecules = new Set(state.allInspirationMoleculeDataList); + if (!molecules.has(action.payload)) { + molecules.add(action.payload); + } + return Object.assign({}, state, { allInspirationMoleculeDataList: [...molecules] }); + case constants.REMOVE_FROM_INSPIRATION_MOLECULE_DATA_LIST: const diminishedInspirationMoleculeDataList = new Set(state.inspirationMoleculeDataList); @@ -331,6 +346,9 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { } return Object.assign({}, state, { inspirationFragmentList: [...diminishedInspirationFragmentList] }); + case constants.SET_ALL_INSPIRATIONS: + return {...state, allInspirations: action.payload}; + case constants.APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET: const setOfMolecules = new Set(state.compoundsToBuyDatasetMap[action.payload.datasetID]); setOfMolecules.add(action.payload.moleculeID); diff --git a/js/components/datasets/redux/selectors.js b/js/components/datasets/redux/selectors.js index 3ceaf3a7e..6e768ce6c 100644 --- a/js/components/datasets/redux/selectors.js +++ b/js/components/datasets/redux/selectors.js @@ -160,6 +160,18 @@ export const isAnyInspirationTurnedOn = createSelector( } ); +export const isAnyInspirationTurnedOnByType = (inspirations, data) => { + let typeLists = new Set(data); + let hasInspirationType = false; + inspirations.forEach(moleculeID => { + if (typeLists.has(moleculeID)) { + hasInspirationType = true; + return hasInspirationType; + } + }); + return hasInspirationType; +}; + export const getFilteredDatasetMoleculeList = createSelector( (_, datasetID) => datasetID, filterDatasetMap, @@ -254,11 +266,11 @@ export const getFilteredDatasetMoleculeList = createSelector( for (let prioAttr of sortedAttributes) { const order = filterProperties[prioAttr].order; - const scoreValueOfA = + let 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 = + let 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]); @@ -284,7 +296,6 @@ export const getFilteredDatasetMoleculeList = createSelector( } } }); - return filteredMolecules; } return datasetMoleculeList; } diff --git a/js/components/datasets/selectedCompoundsList.js b/js/components/datasets/selectedCompoundsList.js index bcc764b97..06c3ff709 100644 --- a/js/components/datasets/selectedCompoundsList.js +++ b/js/components/datasets/selectedCompoundsList.js @@ -11,15 +11,15 @@ import { setIsOpenInspirationDialog } from './redux/actions'; import { CrossReferenceDialog } from './crossReferenceDialog'; import { autoHideDatasetDialogsOnScroll, + resetCrossReferenceDialog, removeDatasetComplex, removeDatasetHitProtein, removeDatasetLigand, - removeDatasetSurface, - resetCrossReferenceDialog + removeDatasetSurface } from './redux/dispatchActions'; -import MoleculeView from '../preview/molecule/moleculeView'; import { NglContext } from '../nglView/nglProvider'; import { VIEWS } from '../../constants/constants'; +import { getMoleculeList } from '../preview/molecule/redux/selectors'; import FileSaver from 'file-saver'; import JSZip from 'jszip'; @@ -238,6 +238,11 @@ export const SelectedCompoundList = memo(({ height }) => { previousItemData={index > 0 && array[index - 1]} nextItemData={index < array?.length && array[index + 1]} removeOfAllSelectedTypes={removeOfAllSelectedTypes} + L={ligandListAllDatasets[data.datasetID].includes(data.molecule.id)} + P={proteinListAllDatasets[data.datasetID].includes(data.molecule.id)} + C={complexListAllDatasets[data.datasetID].includes(data.molecule.id)} + S={surfaceListAllDatasets[data.datasetID].includes(data.molecule.id)} + V={false} /> ))} diff --git a/js/components/header/index.js b/js/components/header/index.js index 30ea8e58b..db831a1f0 100644 --- a/js/components/header/index.js +++ b/js/components/header/index.js @@ -35,7 +35,7 @@ import { URLS } from '../routes/constants'; import { useCombinedRefs } from '../../utils/refHelpers'; import { ComputeSize } from '../../utils/computeSize'; import { DJANGO_CONTEXT } from '../../utils/djangoContext'; -import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../helpers/useEnableUserInteracion'; import { useHistory } from 'react-router-dom'; import { IssueReport } from '../userFeedback/issueReport'; import { IdeaReport } from '../userFeedback/ideaReport'; @@ -92,7 +92,7 @@ export default memo( let history = useHistory(); const classes = useStyles(); const { isLoading, headerNavbarTitle, setHeaderNavbarTitle, headerButtons } = useContext(HeaderContext); - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const [openMenu, setOpenMenu] = useState(false); const [openFunders, setOpenFunders] = useState(false); @@ -283,7 +283,8 @@ export default memo( - {(isLoading === true || disableUserInteraction === true) && ( + {//TODO this needs to be reworked if the optimizations help + (isLoading === true || false === true) && ( )} diff --git a/js/components/nglView/generatingObjects.js b/js/components/nglView/generatingObjects.js index bb73083ec..298867e6d 100644 --- a/js/components/nglView/generatingObjects.js +++ b/js/components/nglView/generatingObjects.js @@ -220,3 +220,10 @@ export const getVectorWithColorByCountOfCompounds = (item, currentVectorCompound } return { ...item, colour: colour, radius: 0.3 }; }; + +export const getRepresentationsByType = (objectsInView, object, objectType, datasetId) => { + let parentItem = `${object.protein_code || object.name}_${objectType}${datasetId ? '_' + datasetId : ''}`; + let objectInView = objectsInView[parentItem]; + var representations = (objectInView && objectInView.representations) || undefined; + return representations; +}; diff --git a/js/components/nglView/renderingObjects.js b/js/components/nglView/renderingObjects.js index b1f755bac..1ed54a329 100644 --- a/js/components/nglView/renderingObjects.js +++ b/js/components/nglView/renderingObjects.js @@ -6,6 +6,7 @@ import { defaultFocus } from './generatingObjects'; import { concatStructures, Selection, Shape, Matrix4 } from 'ngl'; +import {addToPdbCache} from '../../reducers/ngl/actions'; const showSphere = ({ stage, input_dict, object_name, representations }) => { let colour = input_dict.colour; @@ -80,18 +81,53 @@ const renderHitProtein = (ol, representations, orientationMatrix) => { return assignRepresentationArrayToComp(reprArray, comp); }; -const showHitProtein = ({ stage, input_dict, object_name, representations, orientationMatrix }) => { +const loadPdbFile = (url) => { + return fetch(url).then(response => response.text()).then(str => { + return new Blob([str], { type: 'text/plain' }) + }); +}; + +const getNameOfPdb = (url) => { + const parts = url.split('/'); + const last = parts[parts.length - 1]; + return last; +}; + +const getPdb = (url) => (dispatch, getState) => { + const state = getState(); + + const pdbCache = state.nglReducers.pdbCache; + const pdbName = getNameOfPdb(url); + if (pdbCache.hasOwnProperty(pdbName)) { + return new Promise((resolve, reject) => { + resolve(pdbCache[pdbName]) + }); + } else { + return loadPdbFile(url).then(b => { + dispatch(addToPdbCache(pdbName, b)); + return b; + }); + }; +}; + +const showHitProtein = ({ stage, input_dict, object_name, representations, orientationMatrix, dispatch }) => { let stringBlob = new Blob([input_dict.sdf_info], { type: 'text/plain' }); - return Promise.all([ - stage.loadFile(input_dict.prot_url, { ext: 'pdb', defaultAssembly: 'BU1' }), - stage.loadFile(stringBlob, { ext: 'sdf' }), - stage, - defaultFocus, - object_name, - input_dict.colour - ]).then(ol => renderHitProtein(ol, representations, orientationMatrix)); + + return dispatch(getPdb(input_dict.prot_url)).then(pdbBlob => { + return Promise.all([ + stage.loadFile(pdbBlob, { ext: 'pdb', defaultAssembly: 'BU1' }), + stage.loadFile(stringBlob, { ext: 'sdf' }), + stage, + defaultFocus, + object_name, + input_dict.colour + ]); + }).then(ol => { + renderHitProtein(ol, representations, orientationMatrix) + }); }; + const renderComplex = (ol, representations, orientationMatrix) => { let cs = concatStructures( ol[4], diff --git a/js/components/preview/Preview.js b/js/components/preview/Preview.js index a5f890105..b85a4e7a2 100644 --- a/js/components/preview/Preview.js +++ b/js/components/preview/Preview.js @@ -2,7 +2,7 @@ * Created by abradley on 14/04/2018. */ -import React, { memo, useContext, useEffect, useRef, useState } from 'react'; +import React, { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Grid, makeStyles, useTheme, ButtonGroup, Button } from '@material-ui/core'; import NGLView from '../nglView/nglView'; import HitNavigator from './molecule/hitNavigator'; @@ -31,7 +31,7 @@ import { loadDatasetCompoundsWithScores, loadDataSets } from '../datasets/redux/ import { SelectedCompoundList } from '../datasets/selectedCompoundsList'; import { DatasetSelectorMenuButton } from '../datasets/datasetSelectorMenuButton'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; -import { setMoleculeListIsLoading } from '../datasets/redux/actions'; +import { setMoleculeListIsLoading, setAllInspirations } from '../datasets/redux/actions'; const hitNavigatorWidth = 504; @@ -96,6 +96,10 @@ const Preview = memo(({ isStateLoaded, hideProjects }) => { const target_on = useSelector(state => state.apiReducers.target_on); const isTrackingRestoring = useSelector(state => state.trackingReducers.isTrackingCompoundsRestoring); + const all_mol_lists = useSelector(state => state.apiReducers.all_mol_lists); + const moleculeLists = useSelector(state => state.datasetsReducers.moleculeLists); + const isLoadingMoleculeList = useSelector(state => state.datasetsReducers.isLoadingMoleculeList); + /* Loading datasets */ @@ -118,6 +122,44 @@ const Preview = memo(({ isStateLoaded, hideProjects }) => { } }, [customDatasets.length, dispatch, target_on, isTrackingRestoring]); + useEffect(() => { + const allMolsGroupsCount = Object.keys(all_mol_lists || {}).length; + const moleculeListsCount = Object.keys(moleculeLists || {}).length; + if (allMolsGroupsCount > 0 && moleculeListsCount > 0 && !isLoadingMoleculeList) { + const allDatasets = {}; + const allMolsMap = linearizeMoleculesLists(); + const keys = Object.keys(moleculeLists); + keys.forEach(key => { + let dataset = moleculeLists[key]; + let mols = {}; + dataset.forEach(dsMol => { + let inspirations = []; + dsMol.computed_inspirations.forEach(id => { + let lhsMol = allMolsMap[id]; + inspirations.push(lhsMol); + }); + mols[dsMol.id] = inspirations; + }); + allDatasets[key] = mols; + }); + dispatch(setAllInspirations(allDatasets)); + } + }, [all_mol_lists, moleculeLists, isLoadingMoleculeList, linearizeMoleculesLists, dispatch]); + + const linearizeMoleculesLists = useCallback(() => { + const keys = Object.keys(all_mol_lists); + const allMolsMap = {}; + + keys.forEach(key => { + let molList = all_mol_lists[key]; + molList.forEach(mol => { + allMolsMap[mol.id] = mol; + }); + }); + + return allMolsMap; + }, [all_mol_lists]); + const [molGroupsHeight, setMolGroupsHeight] = useState(0); const [filterItemsHeight, setFilterItemsHeight] = useState(0); const [filterItemsHeightDataset, setFilterItemsHeightDataset] = useState(0); diff --git a/js/components/preview/compounds/redux/actions.js b/js/components/preview/compounds/redux/actions.js index 208d124ce..74b157dcf 100644 --- a/js/components/preview/compounds/redux/actions.js +++ b/js/components/preview/compounds/redux/actions.js @@ -29,9 +29,12 @@ export const updateCurrentCompound = ({ id, key, value }) => ({ } }); -export const setCompoundClasses = compoundClasses => ({ +export const setCompoundClasses = (compoundClasses, oldCompoundClasses, value, id) => ({ type: constants.SET_COMPOUND_CLASSES, - payload: compoundClasses + payload: compoundClasses, + oldCompoundClasses: oldCompoundClasses, + value: value, + id: id }); export const resetCompoundClasses = compoundClasses => ({ @@ -39,10 +42,12 @@ export const resetCompoundClasses = compoundClasses => ({ payload: compoundClasses }); -export const setCurrentCompoundClass = currentCompoundClass => { +export const setCurrentCompoundClass = (currentCompoundClass, oldCompoundClass, skipTracking) => { return { type: constants.SET_CURRENT_COMPOUND_CLASS, - payload: currentCompoundClass + payload: currentCompoundClass, + oldCompoundClass: oldCompoundClass, + skipTracking: skipTracking }; }; @@ -57,14 +62,16 @@ export const setShowedCompoundList = compounds => ({ payload: compounds }); -export const addShowedCompoundToList = compoundId => ({ +export const addShowedCompoundToList = (compoundId, item) => ({ type: constants.APPEND_SHOWED_COMPOUND_LIST, - payload: compoundId + payload: compoundId, + item: item }); -export const removeShowedCompoundFromList = compoundId => ({ +export const removeShowedCompoundFromList = (compoundId, item) => ({ type: constants.REMOVE_SHOWED_COMPOUND_LIST, - payload: compoundId + payload: compoundId, + item: item }); export const addSelectedCompoundClass = (classID, compoundID) => ({ diff --git a/js/components/preview/compounds/redux/dispatchActions.js b/js/components/preview/compounds/redux/dispatchActions.js index 658820fa5..fc3c82fca 100644 --- a/js/components/preview/compounds/redux/dispatchActions.js +++ b/js/components/preview/compounds/redux/dispatchActions.js @@ -1,4 +1,10 @@ -import { appendToBuyList, removeFromToBuyList, setToBuyList } from '../../../../reducers/selection/actions'; +import { + appendToBuyList, + removeFromToBuyList, + setToBuyList, + appendToBuyListAll, + removeFromToBuyListAll +} from '../../../../reducers/selection/actions'; import { setCompoundClasses, setCurrentPage, @@ -32,23 +38,29 @@ export const selectAllCompounds = () => (dispatch, getState) => { const moleculeOfVector = getMoleculeOfCurrentVector(state); const smiles = moleculeOfVector && moleculeOfVector.smiles; const currentCompoundClass = state.previewReducers.compounds.currentCompoundClass; + let items = []; for (let key in currentVectorCompoundsFiltered) { for (let index in currentVectorCompoundsFiltered[key]) { if (index !== 'vector') { for (let indexOfCompound in currentVectorCompoundsFiltered[key][index]) { + let compoundId = parseInt(indexOfCompound); var thisObj = { smiles: currentVectorCompoundsFiltered[key][index][indexOfCompound].end, vector: currentVectorCompoundsFiltered[key].vector.split('_')[0], mol: smiles, - class: parseInt(currentCompoundClass) + class: parseInt(currentCompoundClass), + compoundId: compoundId }; - dispatch(appendToBuyList(thisObj)); - dispatch(addSelectedCompoundClass(currentCompoundClass, parseInt(indexOfCompound))); + items.push(thisObj); + dispatch(appendToBuyList(thisObj, compoundId, true)); + dispatch(addSelectedCompoundClass(currentCompoundClass, compoundId)); } } } } + + dispatch(appendToBuyListAll(items)); }; export const onChangeCompoundClassValue = event => (dispatch, getState) => { @@ -59,16 +71,22 @@ export const onChangeCompoundClassValue = event => (dispatch, getState) => { }); // const compoundClasses = state.previewReducers.compounds.compoundClasses; - const newClassDescription = { [event.target.id]: event.target.value }; + let id = event.target.id; + let value = event.target.value; + let oldDescriptionToSet = Object.assign({}, compoundClasses); + const newClassDescription = { [id]: value }; const descriptionToSet = Object.assign(compoundClasses, newClassDescription); - dispatch(setCompoundClasses(descriptionToSet)); + dispatch(setCompoundClasses(descriptionToSet, oldDescriptionToSet, value, id)); }; -export const onKeyDownCompoundClass = event => dispatch => { +export const onKeyDownCompoundClass = event => (dispatch, getState) => { + const state = getState(); + // on Enter if (event.keyCode === 13) { - dispatch(setCurrentCompoundClass(event.target.id)); + let oldCompoundClass = state.previewReducers.compounds.currentCompoundClass; + dispatch(setCurrentCompoundClass(event.target.id, oldCompoundClass)); } }; @@ -131,8 +149,17 @@ const showCompoundNglView = ({ majorViewStage, data, index }) => (dispatch, getS }; export const clearAllSelectedCompounds = majorViewStage => (dispatch, getState) => { - dispatch(setToBuyList([])); const state = getState(); + + let to_buy_list = state.selectionReducers.to_buy_list; + dispatch(clearCompounds(to_buy_list, majorViewStage)); +}; + +const clearCompounds = (items, majorViewStage) => (dispatch, getState) => { + const state = getState(); + + dispatch(removeFromToBuyListAll(items)); + dispatch(setToBuyList([])); // reset objects from nglView and showedCompoundList const currentCompounds = state.previewReducers.compounds.currentCompounds; const showedCompoundList = state.previewReducers.compounds.showedCompoundList; @@ -150,7 +177,7 @@ export const clearAllSelectedCompounds = majorViewStage => (dispatch, getState) // reset configuration dispatch(resetConfiguration()); // reset current compound class - dispatch(dispatch(setCurrentCompoundClass(compoundsColors.blue.key))); + dispatch(dispatch(setCurrentCompoundClass(compoundsColors.blue.key, compoundsColors.blue.key, true))); }; export const handleClickOnCompound = ({ event, data, majorViewStage, index }) => async (dispatch, getState) => { @@ -164,9 +191,9 @@ export const handleClickOnCompound = ({ event, data, majorViewStage, index }) => if (event.shiftKey) { await dispatch(showCompoundNglView({ majorViewStage, data, index })); if (showedCompoundList.find(item => item === index) !== undefined) { - dispatch(removeShowedCompoundFromList(index)); + dispatch(removeShowedCompoundFromList(index, data)); } else { - dispatch(addShowedCompoundToList(index)); + dispatch(addShowedCompoundToList(index, data)); } } else { let isSelectedID; @@ -180,11 +207,48 @@ export const handleClickOnCompound = ({ event, data, majorViewStage, index }) => if (isSelectedID !== undefined) { await dispatch(removeSelectedCompoundClass(index)); - dispatch(removeFromToBuyList(data)); + dispatch(removeFromToBuyList(data, index)); } else { await dispatch(addSelectedCompoundClass(currentCompoundClass, index)); - dispatch(appendToBuyList(Object.assign({}, data, { class: currentCompoundClass }))); + dispatch(appendToBuyList(Object.assign({}, data, { class: currentCompoundClass })), index); + } + } +}; + +export const handleBuyList = ({ isSelected, data, compoundId }) => (dispatch, getState) => { + const state = getState(); + const currentCompoundClass = state.previewReducers.compounds.currentCompoundClass; + + dispatch(setHighlightedCompoundId(compoundId)); + + if (isSelected === false) { + dispatch(removeSelectedCompoundClass(compoundId)); + dispatch(removeFromToBuyList(data, compoundId, true)); + } else { + dispatch(addSelectedCompoundClass(currentCompoundClass, compoundId)); + dispatch(appendToBuyList(Object.assign({}, data, { class: currentCompoundClass }), compoundId, true)); + } +}; + +export const handleBuyListAll = ({ isSelected, items, majorViewStage }) => (dispatch, getState) => { + if (isSelected === false) { + dispatch(clearCompounds(items, majorViewStage)); + } else { + for (var item in items) { + let index = item.compoundId; + dispatch(appendToBuyList(item, index, true)); + dispatch(addSelectedCompoundClass(item.class, index)); } + dispatch(appendToBuyListAll(items)); + } +}; + +export const handleShowVectorCompound = ({ isSelected, data, index, majorViewStage }) => async (dispatch, getState) => { + await dispatch(showCompoundNglView({ majorViewStage, data, index })); + if (isSelected === false) { + dispatch(removeShowedCompoundFromList(index, data)); + } else { + dispatch(addShowedCompoundToList(index, data)); } }; diff --git a/js/components/preview/molecule/moleculeList.js b/js/components/preview/molecule/moleculeList.js index 77f6eadd9..c34d7f3a3 100644 --- a/js/components/preview/molecule/moleculeList.js +++ b/js/components/preview/molecule/moleculeList.js @@ -28,7 +28,7 @@ import { ComputeSize } from '../../../utils/computeSize'; import { moleculeProperty } from './helperConstants'; import { VIEWS } from '../../../constants/constants'; import { NglContext } from '../../nglView/nglProvider'; -import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; import classNames from 'classnames'; import { addVector, @@ -250,6 +250,9 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei const firstLoad = useSelector(state => state.selectionReducers.firstLoad); const target_on = useSelector(state => state.apiReducers.target_on); const mol_group_on = useSelector(state => state.apiReducers.mol_group_on); + + const allInspirationMoleculeDataList = useSelector(state => state.datasetsReducers.allInspirationMoleculeDataList); + const mol_group_list = useSelector(state => state.apiReducers.mol_group_list); const all_mol_lists = useSelector(state => state.apiReducers.all_mol_lists); const directDisplay = useSelector(state => state.apiReducers.direct_access); @@ -268,7 +271,7 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei const filterRef = useRef(); - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); if (directDisplay && directDisplay.target) { target = directDisplay.target; @@ -304,6 +307,21 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei return result; }, [getAllMoleculeList]); + const addSelectedMoleculesFromUnselectedSites = useCallback((joinedMoleculeLists, list) => { + const result = [...joinedMoleculeLists]; + list?.forEach(moleculeID => { + const foundJoinedMolecule = result.find(mol => mol.id === moleculeID); + if (!foundJoinedMolecule) { + const molecule = getAllMoleculeList.find(mol => mol.id === moleculeID); + if (molecule) { + result.push(molecule); + } + } + }); + + return result; + }, [getAllMoleculeList]); + joinedMoleculeLists = useMemo( () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, proteinList), [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, proteinList] @@ -329,11 +347,6 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, vectorOnList] ); - // Used for MoleculeListSortFilterDialog when using textSearch - // Also used for displaying filter, since using the original would perform deadlock when creating a filter which matches - // 0 molecules - const joinedMoleculeListsCopy = useMemo(() => [...joinedMoleculeLists], [joinedMoleculeLists]); - if (!isActiveFilter) { // default sort is by site joinedMoleculeLists.sort((a, b) => a.site - b.site || a.number - b.number); @@ -560,6 +573,8 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei }; const removeOfAllSelectedTypes = () => { + let molecules = [...getJoinedMoleculeList, ...allInspirationMoleculeDataList]; + proteinList?.forEach(moleculeID => { const foundedMolecule = joinedMoleculeLists?.find(mol => mol.id === moleculeID); dispatch(removeHitProtein(majorViewStage, foundedMolecule, colourList[foundedMolecule.id % colourList.length])); @@ -701,7 +716,7 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei ) }} onChange={handleSearch} - disabled={disableUserInteraction} + disabled={false || (getJoinedMoleculeList && getJoinedMoleculeList.length === 0)} />, { - onButtonToggle('ligand'); - }} - disabled={disableUserInteraction} + onClick={() => onButtonToggle('ligand')} + disabled={false} > L @@ -850,10 +863,8 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei [classes.contColButtonSelected]: isProteinOn, [classes.contColButtonHalfSelected]: isProteinOn === null })} - onClick={() => { - onButtonToggle('protein'); - }} - disabled={disableUserInteraction} + onClick={() => onButtonToggle('protein')} + disabled={false} > P @@ -868,10 +879,8 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei [classes.contColButtonSelected]: isComplexOn, [classes.contColButtonHalfSelected]: isComplexOn === null })} - onClick={() => { - onButtonToggle('complex'); - }} - disabled={disableUserInteraction} + onClick={() => onButtonToggle('complex')} + disabled={false} > C @@ -915,6 +924,11 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei previousItemData={index > 0 && array[index - 1]} nextItemData={index < array?.length && array[index + 1]} removeOfAllSelectedTypes={removeOfAllSelectedTypes} + L={fragmentDisplayList.includes(data.id)} + P={proteinList.includes(data.id)} + C={complexList.includes(data.id)} + S={surfaceList.includes(data.id)} + V={vectorOnList.includes(data.id)} selectMoleculeSite={selectMoleculeSite} /> ))} diff --git a/js/components/preview/molecule/moleculeListSortFilterDialog.js b/js/components/preview/molecule/moleculeListSortFilterDialog.js index 0d659ddb8..2b727af1f 100644 --- a/js/components/preview/molecule/moleculeListSortFilterDialog.js +++ b/js/components/preview/molecule/moleculeListSortFilterDialog.js @@ -173,6 +173,9 @@ export const MoleculeListSortFilterDialog = memo( }, [joinedMoleculeLists]); const [initState, setInitState] = useState(initialize()); + + filter = filter || initState; + const [filteredCount, setFilteredCount] = useState(getFilteredMoleculesCount(joinedMoleculeLists, filter)); const [predefinedFilter, setPredefinedFilter] = useState(filter.predefined); diff --git a/js/components/preview/molecule/moleculeView.js b/js/components/preview/molecule/moleculeView.js index efaa161b2..c2667f31d 100644 --- a/js/components/preview/molecule/moleculeView.js +++ b/js/components/preview/molecule/moleculeView.js @@ -5,13 +5,13 @@ import React, { memo, useEffect, useState, useRef, useContext, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Grid, Button, makeStyles, Typography, Tooltip, IconButton } from '@material-ui/core'; -import { MyLocation, ArrowDownward, ArrowUpward } from '@material-ui/icons'; +import { MyLocation, ArrowDownward, ArrowUpward, FormatListBulletedTwoTone, FormatListBulletedSharp, FormatListNumbered } from '@material-ui/icons'; import SVGInline from 'react-svg-inline'; import classNames from 'classnames'; import { VIEWS } from '../../../constants/constants'; import { loadFromServer } from '../../../utils/genericView'; import { NglContext } from '../../nglView/nglProvider'; -import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; import { addVector, removeVector, @@ -25,13 +25,17 @@ import { removeDensity, addLigand, removeLigand, - searchMoleculeGroupByMoleculeID + searchMoleculeGroupByMoleculeID, + getMolImage } from './redux/dispatchActions'; import { setSelectedAll, setDeselectedAll } from '../../../reducers/selection/actions'; import { base_url } from '../../routes/constants'; import { moleculeProperty } from './helperConstants'; import { centerOnLigandByMoleculeID } from '../../../reducers/ngl/dispatchActions'; import { SvgTooltip } from '../../common'; +import { OBJECT_TYPE } from '../../nglView/constants'; +import { getRepresentationsByType } from '../../nglView/generatingObjects'; +import {MOL_TYPE} from './redux/constants'; const useStyles = makeStyles(theme => ({ container: { @@ -206,44 +210,42 @@ const MoleculeView = memo( previousItemData, nextItemData, removeOfAllSelectedTypes, + L, P, C, S, V, selectMoleculeSite }) => { // const [countOfVectors, setCountOfVectors] = useState('-'); // const [cmpds, setCmpds] = useState('-'); const selectedAll = useRef(false); + const ref = useRef(null); const currentID = (data && data.id) || undefined; const classes = useStyles(); const key = 'mol_image'; const [moleculeGroupID, setMoleculeGroupID] = useState(); const dispatch = useDispatch(); - const proteinList = useSelector(state => state.selectionReducers.proteinList); - const complexList = useSelector(state => state.selectionReducers.complexList); - const surfaceList = useSelector(state => state.selectionReducers.surfaceList); - const densityList = useSelector(state => state.selectionReducers.densityList); - const fragmentDisplayList = useSelector(state => state.selectionReducers.fragmentDisplayList); - const vectorOnList = useSelector(state => state.selectionReducers.vectorOnList); const target_on_name = useSelector(state => state.apiReducers.target_on_name); const filter = useSelector(state => state.selectionReducers.filter); const url = new URL(base_url + '/api/molimg/' + data.id + '/'); const [img_data, setImg_data] = useState(img_data_init); + const objectsInView = useSelector(state => state.nglReducers.objectsInView) || {}; + const { getNglView } = useContext(NglContext); const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; - const isLigandOn = (currentID && fragmentDisplayList.includes(currentID)) || false; - const isProteinOn = (currentID && proteinList.includes(currentID)) || false; - const isComplexOn = (currentID && complexList.includes(currentID)) || false; - const isSurfaceOn = (currentID && surfaceList.includes(currentID)) || false; - const isDensityOn = (currentID && densityList.includes(currentID)) || false; - const isVectorOn = (currentID && vectorOnList.includes(currentID)) || false; + const isLigandOn = L; + const isProteinOn = P; + const isComplexOn = C; + const isSurfaceOn = S; + // const isDensityOn = false; + const isVectorOn = V; const hasAllValuesOn = isLigandOn && isProteinOn && isComplexOn; const hasSomeValuesOn = !hasAllValuesOn && (isLigandOn || isProteinOn || isComplexOn); - const areArrowsVisible = isLigandOn || isProteinOn || isComplexOn || isSurfaceOn || isDensityOn || isVectorOn; + const areArrowsVisible = isLigandOn || isProteinOn || isComplexOn || isSurfaceOn || isVectorOn; - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const oldUrl = useRef(''); const setOldUrl = url => { @@ -281,37 +283,10 @@ const MoleculeView = memo( // componentDidMount useEffect(() => { - if (refOnCancel.current === undefined) { - let onCancel = () => {}; - Promise.all([ - loadFromServer({ - width: imageHeight, - height: imageWidth, - key, - old_url: oldUrl.current, - setImg_data, - setOld_url: newUrl => setOldUrl(newUrl), - url, - cancel: onCancel - }) - /* api({ url: `${base_url}/api/vector/${data.id}` }).then(response => { - const vectors = response.data.vectors['3d']; - setCountOfVectors(generateObjectList(vectors).length); - }), - api({ url: `${base_url}/api/graph/${data.id}` }).then(response => { - setCmpds(getTotalCountOfCompounds(response.data.graph)); - })*/ - ]).catch(error => { - throw new Error(error); - }); - refOnCancel.current = onCancel; - } - return () => { - if (refOnCancel) { - refOnCancel.current(); - } - }; - }, [complexList, data.id, data.smiles, fragmentDisplayList, imageHeight, url, vectorOnList, imageWidth]); + dispatch(getMolImage(data.id, MOL_TYPE.HIT, imageHeight, imageWidth)).then(i => { + setImg_data(i); + }); + }, [data.id, data.smiles, imageHeight, url, imageWidth, dispatch]); useEffect(() => { if (searchMoleculeGroup) { @@ -459,13 +434,13 @@ const MoleculeView = memo( dispatch(addDensity(stage, data, colourToggle)); }; - const onDensity = () => { - if (isDensityOn === false) { - addNewDensity(); - } else { - removeSelectedDensity(); - } - }; + // const onDensity = () => { + // if (isDensityOn === false) { + // addNewDensity(); + // } else { + // removeSelectedDensity(); + // } + // }; const removeSelectedVector = () => { dispatch(removeVector(stage, data)); @@ -526,20 +501,25 @@ const MoleculeView = memo( const moveSelectedMolSettings = newItemDataset => { if (newItemDataset) { if (isLigandOn) { - dispatch(addLigand(stage, newItemDataset, colourToggle)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.LIGAND); + dispatch(addLigand(stage, newItemDataset, colourToggle, false, representations)); } if (isProteinOn) { - dispatch(addHitProtein(stage, newItemDataset, colourToggle)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.HIT_PROTEIN); + dispatch(addHitProtein(stage, newItemDataset, colourToggle, representations)); } if (isComplexOn) { - dispatch(addComplex(stage, newItemDataset, colourToggle)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.COMPLEX); + dispatch(addComplex(stage, newItemDataset, colourToggle, representations)); } if (isSurfaceOn) { - dispatch(addSurface(stage, newItemDataset, colourToggle)); - } - if (isDensityOn) { - dispatch(addDensity(stage, newItemDataset, colourToggle)); + let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.SURFACE); + dispatch(addSurface(stage, newItemDataset, colourToggle, representations)); } + // if (isDensityOn) { + // let representations = getRepresentationsByType(objectsInView, data, OBJECT_TYPE.DENSITY); + // dispatch(addDensity(stage, newItemDataset, colourToggle, representations)); + // } if (isVectorOn) { dispatch(addVector(stage, newItemDataset)).catch(error => { throw new Error(error); @@ -548,12 +528,26 @@ const MoleculeView = memo( } }; + const scrollToElement = element => { + element.scrollIntoView({ + behavior: 'auto', + block: 'nearest', + inline: 'nearest' + }); + }; + const handleClickOnDownArrow = () => { + const refNext = ref.current.nextSibling; + scrollToElement(refNext); + removeOfAllSelectedTypes(); moveSelectedMolSettings(nextItemData); }; const handleClickOnUpArrow = () => { + const refPrevious = ref.current.previousSibling; + scrollToElement(refPrevious); + removeOfAllSelectedTypes(); moveSelectedMolSettings(previousItemData); }; @@ -562,7 +556,7 @@ const MoleculeView = memo( return ( <> - + {/* Site number */} @@ -597,7 +591,7 @@ const MoleculeView = memo( onClick={() => { dispatch(centerOnLigandByMoleculeID(stage, data?.id)); }} - disabled={disableUserInteraction || !isLigandOn} + disabled={false || !isLigandOn} > @@ -618,14 +612,14 @@ const MoleculeView = memo( )} onClick={() => { // always deselect all if are selected only some of options - selectedAll.current = hasSomeValuesOn ? false : !selectedAll.current; + selectedAll.current = hasSomeValuesOn || hasAllValuesOn ? false : !selectedAll.current; setCalledFromAll(); onLigand(true); onProtein(true); onComplex(true); }} - disabled={disableUserInteraction} + disabled={false} > A @@ -639,7 +633,7 @@ const MoleculeView = memo( [classes.contColButtonSelected]: isLigandOn })} onClick={() => onLigand()} - disabled={disableUserInteraction} + disabled={false} > L @@ -653,7 +647,7 @@ const MoleculeView = memo( [classes.contColButtonSelected]: isProteinOn })} onClick={() => onProtein()} - disabled={disableUserInteraction} + disabled={false} > P @@ -668,7 +662,7 @@ const MoleculeView = memo( [classes.contColButtonSelected]: isComplexOn })} onClick={() => onComplex()} - disabled={disableUserInteraction} + disabled={false} > C @@ -682,7 +676,7 @@ const MoleculeView = memo( [classes.contColButtonSelected]: isSurfaceOn })} onClick={() => onSurface()} - disabled={disableUserInteraction} + disabled={false} > S @@ -694,10 +688,10 @@ const MoleculeView = memo( @@ -711,7 +705,7 @@ const MoleculeView = memo( [classes.contColButtonSelected]: isVectorOn })} onClick={() => onVector()} - disabled={disableUserInteraction} + disabled={false} > V @@ -753,7 +747,7 @@ const MoleculeView = memo( @@ -763,7 +757,7 @@ const MoleculeView = memo( diff --git a/js/components/preview/molecule/redux/actions.js b/js/components/preview/molecule/redux/actions.js index 577e64328..0415ce91a 100644 --- a/js/components/preview/molecule/redux/actions.js +++ b/js/components/preview/molecule/redux/actions.js @@ -9,3 +9,8 @@ export const reloadMoleculeReducer = newState => ({ type: constants.SET_SORT_DIALOG_OPEN, payload: newState }); + +export const addImageToCache = (molId, image) => ({ + type: constants.ADD_IMAGE_TO_CACHE, + payload: {molId: molId, image: image} +}); diff --git a/js/components/preview/molecule/redux/constants.js b/js/components/preview/molecule/redux/constants.js index 387e19149..62f9fac26 100644 --- a/js/components/preview/molecule/redux/constants.js +++ b/js/components/preview/molecule/redux/constants.js @@ -2,7 +2,8 @@ const prefix = 'PREVIEW_MOLECULE_'; export const constants = { SET_SORT_DIALOG_OPEN: prefix + 'SET_SORT_DIALOG_OPEN', - RELOAD_REDUCER: prefix + 'RELOAD_REDUCER' + RELOAD_REDUCER: prefix + 'RELOAD_REDUCER', + ADD_IMAGE_TO_CACHE: prefix + 'ADD_IMAGE_TO_CACHE' }; export const MOL_ATTR = { @@ -88,3 +89,8 @@ export const MOL_ATTR = { }; export const MOL_ATTRIBUTES = Object.values(MOL_ATTR); + +export const MOL_TYPE = { + HIT: 'HIT', + DATASET: 'DATASET' +}; diff --git a/js/components/preview/molecule/redux/dispatchActions.js b/js/components/preview/molecule/redux/dispatchActions.js index 7d4b8234e..c419d947f 100644 --- a/js/components/preview/molecule/redux/dispatchActions.js +++ b/js/components/preview/molecule/redux/dispatchActions.js @@ -45,6 +45,8 @@ import { getMoleculeOfCurrentVector } from '../../../../reducers/selection/selec import { resetCurrentCompoundsSettings } from '../../compounds/redux/actions'; import { selectMoleculeGroup } from '../../moleculeGroups/redux/dispatchActions'; import { setDirectAccess, setDirectAccessProcessed } from '../../../../reducers/api/actions'; +import {MOL_TYPE} from './constants'; +import {addImageToCache} from './actions'; /** * Convert the JSON into a list of arrow objects @@ -179,11 +181,12 @@ export const removeVector = (stage, data, skipTracking = false) => async (dispat dispatch(setVectorList(vector_list.filter(item => item.moleculeId !== data.id))); }; -export const addHitProtein = (stage, data, colourToggle, skipTracking = false) => dispatch => { +export const addHitProtein = (stage, data, colourToggle, skipTracking = false, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateHitProteinObject(data, colourToggle, base_url)), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -203,11 +206,12 @@ export const removeHitProtein = (stage, data, colourToggle, skipTracking = false dispatch(removeFromProteinList(generateMoleculeId(data), skipTracking)); }; -export const addComplex = (stage, data, colourToggle, skipTracking = false) => dispatch => { +export const addComplex = (stage, data, colourToggle, skipTracking = false, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateComplexObject(data, colourToggle, base_url)), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -227,11 +231,12 @@ export const removeComplex = (stage, data, colourToggle, skipTracking = false) = dispatch(removeFromComplexList(generateMoleculeId(data), skipTracking)); }; -export const addSurface = (stage, data, colourToggle, skipTracking = false) => dispatch => { +export const addSurface = (stage, data, colourToggle, skipTracking = false, representations = undefined) => dispatch => { dispatch( loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateSurfaceObject(data, colourToggle, base_url)), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -251,13 +256,14 @@ export const removeSurface = (stage, data, colourToggle, skipTracking = false) = dispatch(removeFromSurfaceList(generateMoleculeId(data), skipTracking)); }; -export const addDensity = (stage, data, colourToggle) => dispatch => { +export const addDensity = (stage, data, colourToggle, representations = undefined) => dispatch => { console.log('TODO'); return; dispatch( loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateDensityObject(data, colourToggle, base_url)), stage, + previousRepresentations: representations, orientationMatrix: null }) ).finally(() => { @@ -279,7 +285,7 @@ export const removeDensity = (stage, data, colourToggle) => dispatch => { dispatch(removeFromDensityList(generateMoleculeId(data))); }; -export const addLigand = (stage, data, colourToggle, centerOn = false, skipTracking = false) => ( +export const addLigand = (stage, data, colourToggle, centerOn = false, skipTracking = false, representations = undefined) => ( dispatch, getState ) => { @@ -289,7 +295,7 @@ export const addLigand = (stage, data, colourToggle, centerOn = false, skipTrack loadObject({ target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, generateMoleculeObject(data, colourToggle)), stage, - undefined + previousRepresentations: representations }) ).finally(() => { const ligandOrientation = stage.viewerControls.getOrientation(); @@ -395,21 +401,43 @@ export const hideAllSelectedMolecules = (stage, currentMolecules) => (dispatch, dispatch(setHideAll(data)); }; -export const searchMoleculeGroupByMoleculeID = moleculeID => (dispatch, getState) => - api({ url: `${base_url}/api/molgroup/?mol_id=${moleculeID}` }).then(response => { - let resultMolGroupID = null; - const molGroupID = response?.data?.results[0]?.id; - const mol_group_list = getState().apiReducers.mol_group_list; +// export const searchMoleculeGroupByMoleculeID = moleculeID => (dispatch, getState) => +// api({ url: `${base_url}/api/molgroup/?mol_id=${moleculeID}` }).then(response => { +// let resultMolGroupID = null; +// const molGroupID = response?.data?.results[0]?.id; +// const mol_group_list = getState().apiReducers.mol_group_list; + +// if (mol_group_list && Array.isArray(mol_group_list) && molGroupID) { +// mol_group_list.forEach((item, index) => { +// if (item.id === molGroupID) { +// resultMolGroupID = index + 1; +// } +// }); +// } +// return Promise.resolve(resultMolGroupID); +// }); + +export const searchMoleculeGroupByMoleculeID = moleculeId => (dispatch, getState) => { + const state = getState(); + const all_mol_lists = state.apiReducers.all_mol_lists; + + let resultMolGroupID = null; + const molGroupIds = Object.keys(all_mol_lists); + for (let groupId of molGroupIds) { + const mols = all_mol_lists[groupId]; + for (let mol of mols) { + if (mol.id === moleculeId) { + resultMolGroupID = groupId; + break; + }; + }; + if (resultMolGroupID != null) { + break; + }; + }; - if (mol_group_list && Array.isArray(mol_group_list) && molGroupID) { - mol_group_list.forEach((item, index) => { - if (item.id === molGroupID) { - resultMolGroupID = index + 1; - } - }); - } - return Promise.resolve(resultMolGroupID); - }); + return Promise.resolve(resultMolGroupID); +} export const applyDirectSelection = (stage, stageSummaryView) => (dispatch, getState) => { const state = getState(); @@ -472,3 +500,52 @@ export const applyDirectSelection = (stage, stageSummaryView) => (dispatch, getS dispatch(setDirectAccessProcessed(true)); } }; + +export const getMolImage = (molId, molType, width, height) => (dispatch, getState) => { + const state = getState(); + + const imageCache = state.previewReducers.molecule.imageCache; + + const molIdStr = molId.toString(); + if (imageCache.hasOwnProperty(molIdStr)) { + return new Promise((resolve, reject) => { + resolve(imageCache[molIdStr]); + }); + } else { + return loadMolImage(molId, molType, width, height).then(i => { + if (!imageCache.hasOwnProperty(molIdStr)) { + dispatch(addImageToCache(molId.toString(), i)); + }; + return i; + }); + } +}; + +export const loadMolImage = (molId, molType, width, height) => { + let url = undefined; + if (molType === MOL_TYPE.HIT) { + url = new URL(`${base_url}/api/molimg/${molId}/`); + url.searchParams.append('width', width); + url.searchParams.append('height', height); + } else if (molType === MOL_TYPE.DATASET) { + url = new URL(`${base_url}/viewer/img_from_smiles/`); + url.searchParams.append('width', width); + url.searchParams.append('height', height); + url.searchParams.append('smiles', molId); + } else { + console.error('Trying to load image for unknown molecule type.'); + return Promise.resolve(); + } + + let onCancel = () => {}; + return api({ + url, + onCancel + }).then(response => { + if (molType === MOL_TYPE.HIT) { + return response.data['mol_image']; + } else { + return response.data; + } + }); +}; diff --git a/js/components/preview/molecule/redux/reducer.js b/js/components/preview/molecule/redux/reducer.js index 4c6311f50..81348038d 100644 --- a/js/components/preview/molecule/redux/reducer.js +++ b/js/components/preview/molecule/redux/reducer.js @@ -1,7 +1,8 @@ import { constants } from './constants'; export const INITIAL_STATE = { - sortDialogOpen: false + sortDialogOpen: false, + imageCache: {} }; export const molecule = (state = INITIAL_STATE, action = {}) => { @@ -14,6 +15,11 @@ export const molecule = (state = INITIAL_STATE, action = {}) => { case constants.RELOAD_REDUCER: return Object.assign({}, state, { ...action.payload }); + case constants.ADD_IMAGE_TO_CACHE: + return {...state, imageCache: { + ...state.imageCache, [action.payload.molId]: action.payload.image + }}; + default: return state; } diff --git a/js/components/preview/molecule/redux/selectors.js b/js/components/preview/molecule/redux/selectors.js index 9fe926a7e..15c6d1222 100644 --- a/js/components/preview/molecule/redux/selectors.js +++ b/js/components/preview/molecule/redux/selectors.js @@ -29,10 +29,31 @@ export const selectJoinedMoleculeList = createSelector( }); }); } + return joinedMoleculeLists; } ); +export const getMoleculeList = createSelector( + getAllMolecules, + getMoleculeGroupLists, + (all_mol_lists, mol_group_list) => { + let cachedDataArray = []; + if (mol_group_list) { + mol_group_list.forEach(obj => { + const cachedData = all_mol_lists[obj.id]; + + if (cachedData && Array.isArray(cachedData)) { + cachedDataArray.push(...cachedData); + } else if (cachedData && cachedData.results && Array.isArray(cachedData.results)) { + cachedDataArray.push(...cachedData.results); + } + }); + } + + return cachedDataArray; + } +); export const selectAllMoleculeList = createSelector( getAllMolecules, getMoleculeGroupLists, @@ -51,4 +72,4 @@ export const selectAllMoleculeList = createSelector( return allMoleculesList; } -); \ No newline at end of file +); diff --git a/js/components/preview/moleculeGroups/molGroupChecklist.js b/js/components/preview/moleculeGroups/molGroupChecklist.js index 9bf99efe6..b7c3fc6a3 100644 --- a/js/components/preview/moleculeGroups/molGroupChecklist.js +++ b/js/components/preview/moleculeGroups/molGroupChecklist.js @@ -3,7 +3,7 @@ import { Grid, makeStyles, Checkbox, Tooltip, Typography } from '@material-ui/co import { useDispatch, useSelector } from 'react-redux'; import { heightOfBody } from './molGroupSelector'; import { VIEWS } from '../../../constants/constants'; -import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; import { NglContext } from '../../nglView/nglProvider'; import { onSelectMoleculeGroup } from './redux/dispatchActions'; @@ -44,7 +44,7 @@ const useStyles = makeStyles(theme => ({ const molGroupChecklist = memo(({}) => { const classes = useStyles(); - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const { getNglView } = useContext(NglContext); const stageSummaryView = getNglView(VIEWS.SUMMARY_VIEW) && getNglView(VIEWS.SUMMARY_VIEW).stage; const majorViewStage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; @@ -74,7 +74,7 @@ const molGroupChecklist = memo(({}) => { color="primary" checked={checked} onChange={event => dispatch(onSelectMoleculeGroup({ moleculeGroup, stageSummaryView, majorViewStage, selectGroup: event.target.checked }))} - disabled={disableUserInteraction} + disabled={false} /> diff --git a/js/components/preview/moleculeGroups/molGroupSelector.js b/js/components/preview/moleculeGroups/molGroupSelector.js index 0a9841490..297ebeb3c 100644 --- a/js/components/preview/moleculeGroups/molGroupSelector.js +++ b/js/components/preview/moleculeGroups/molGroupSelector.js @@ -9,7 +9,7 @@ import { useDispatch } from 'react-redux'; import { VIEWS } from '../../../constants/constants'; import { withLoadingMolGroupList } from './withLoadingMolGroupList'; import { NglContext } from '../../nglView/nglProvider'; -import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; +// import { useDisableUserInteraction } from '../../helpers/useEnableUserInteracion'; import { clearMoleculeGroupSelection } from './redux/dispatchActions'; export const heightOfBody = '164px'; @@ -34,7 +34,7 @@ const MolGroupSelector = memo(({ handleHeightChange }) => { const ref = useRef(null); const { getNglView } = useContext(NglContext); - const disableUserInteraction = useDisableUserInteraction(); + // const disableUserInteraction = useDisableUserInteraction(); const dispatch = useDispatch(); return ( @@ -47,7 +47,7 @@ const MolGroupSelector = memo(({ handleHeightChange }) => { headerActions={[ - + , @@ -64,9 +70,9 @@ export const withSnapshotManagement = WrappedComponent => { @@ -76,7 +82,7 @@ export const withSnapshotManagement = WrappedComponent => { color="primary" size="small" startIcon={} - disabled={disableShareButton || disableUserInteraction} + disabled={disableShareButton || false} onClick={() => { dispatch(saveAndShareSnapshot(nglViewList)); }} @@ -91,25 +97,11 @@ export const withSnapshotManagement = WrappedComponent => { setSnackBarTitle(null); setHeaderNavbarTitle(''); }; - }, [ - enableSaveButton, - dispatch, - sessionTitle, - setHeaderNavbarTitle, - setHeaderButtons, - setSnackBarTitle, - targetIdList, - targetName, - setSnackBarColor, - projectId, - disableUserInteraction, - currentSnapshotID, - currentProject, - disableShareButton, - target, - nglViewList - ]); + }, [enableSaveButton, dispatch, sessionTitle, setHeaderNavbarTitle, setHeaderButtons, setSnackBarTitle, targetIdList, targetName, setSnackBarColor, projectId, currentSnapshotID, currentProject, disableShareButton, target, nglViewList, currentSnapshot.id, history]); - return ; + return ; }); }; diff --git a/js/components/tracking/EditableText.js b/js/components/tracking/EditableText.js new file mode 100644 index 000000000..91549d9de --- /dev/null +++ b/js/components/tracking/EditableText.js @@ -0,0 +1,68 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { makeStyles, TextField, IconButton, Tooltip, Grid } from '@material-ui/core'; +import { Edit } from '@material-ui/icons'; + +const useStyles = makeStyles(theme => ({ + search: { + width: '100%' + }, + fontSizeSmall: { + fontSize: '0.82rem' + } +})); + +const EditableInput = ({ dataText, index, updateText }) => { + const inputRef = useRef(null); + const [inputVisible, setInputVisible] = useState(false); + const [text, setText] = useState(dataText); + const classes = useStyles(); + + const onClickOutSide = e => { + if (inputRef.current && !inputRef.current.contains(e.target)) { + setInputVisible(false); + if (updateText && text !== dataText) { + updateText(text); + } + } + }; + + useEffect(() => { + // Handle outside clicks on mounted state + if (inputVisible) { + document.addEventListener('mousedown', onClickOutSide); + } + + // This is a necessary step to "dismount" unnecessary events when we destroy the component + return () => { + document.removeEventListener('mousedown', onClickOutSide); + }; + }); + + return ( + + {inputVisible ? ( + { + setText(e.target.value); + }} + /> + ) : ( + + { setInputVisible(true)}>{text}} + { + setInputVisible(true)}> + + + + + } + + )} + + ); +}; + +export default EditableInput; diff --git a/js/components/tracking/timelineView.js b/js/components/tracking/timelineView.js new file mode 100644 index 000000000..28bd99e14 --- /dev/null +++ b/js/components/tracking/timelineView.js @@ -0,0 +1,228 @@ +import React, { useState, useRef, memo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { makeStyles, IconButton, Tooltip, Grid, Box, Chip } from '@material-ui/core'; +import { Check, Clear, Warning, Favorite, Star } from '@material-ui/icons'; +import { actionAnnotation } from '../../reducers/tracking/constants'; +import { TimelineEvent } from 'react-event-timeline'; +import EditableText from './EditableText'; +import palette from '../../theme/palette'; +import { updateTrackingActions } from '../../reducers/tracking/dispatchActions'; +import { actionType } from '../../reducers/tracking/constants'; +import Gallery from 'react-grid-gallery'; + +const useStyles = makeStyles(theme => ({ + headerGrid: { + height: '15px' + }, + grid: { + height: 'inherit' + }, + iconButton: { + padding: '6px', + paddingTop: '0px' + }, + timelineEvent: { + borderBottom: '1px dashed ' + palette.divider, + paddingBottom: '10px' + }, + title: { + position: 'relative', + paddingLeft: '45px', + textAlign: 'left', + fontWeight: 'bold' + }, + titleMargin: { + marginRight: '5px' + } +})); + +const TimelineView = memo(({ data, index }) => { + const dispatch = useDispatch(); + const classes = useStyles(); + + const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); + let isSelected = currentSnapshotID === data.object_id; + + const ref = useRef(null); + const [isHovering, setIsHovering] = useState(false); + const [updatedIcon, setUpdatedIcon] = useState(null); + + const getActionIcon = annotation => { + if (annotation) { + switch (annotation) { + case actionAnnotation.CHECK: + return ; + case actionAnnotation.CLEAR: + return ; + case actionAnnotation.WARNING: + return ; + case actionAnnotation.FAVORITE: + return ; + case actionAnnotation.STAR: + return ; + default: + return ; + } + } else { + return ; + } + }; + + const annotationActions = [ + { + setUpdatedIcon(actionAnnotation.CHECK); + updateDataAnnotation(actionAnnotation.CHECK); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.CLEAR); + updateDataAnnotation(actionAnnotation.CLEAR); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.WARNING); + updateDataAnnotation(actionAnnotation.WARNING); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.FAVORITE); + updateDataAnnotation(actionAnnotation.FAVORITE); + }} + > + + + + , + { + setUpdatedIcon(actionAnnotation.STAR); + updateDataAnnotation(actionAnnotation.STAR); + }} + > + + + + + ]; + + const updateDataText = text => { + data.text = text; + dispatch(updateTrackingActions(data)); + }; + + const updateDataAnnotation = annotation => { + data.annotation = annotation; + dispatch(updateTrackingActions(data)); + }; + + const images = [ + { + src: data.image, + thumbnail: data.image, + thumbnailWidth: 0, + thumbnailHeight: 0, + caption: data.object_name + } + ]; + + return ( +
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)}> + {data.type && data.type === actionType.SNAPSHOT ? ( + <> + + {isSelected && ( + + + + )} + { + + {`${data.text}`} + + } + { + + + + } + + + ) : ( + <> + + + { + + + + } + {isHovering && ( + + {annotationActions && + annotationActions.map((action, index) => ( + + {action} + + ))} + + )} + +
+ } + createdAt={new Date(data.timestamp).toLocaleString()} + icon={updatedIcon && updatedIcon != null ? getActionIcon(updatedIcon) : getActionIcon(data.annotation)} + iconColor={palette.primary.main} + className={classes.timelineEvent} + > + + )} + + ); +}); + +TimelineView.displayName = 'TimelineView'; +export default TimelineView; diff --git a/js/components/tracking/trackingModal.js b/js/components/tracking/trackingModal.js index d47761e84..09cb10118 100644 --- a/js/components/tracking/trackingModal.js +++ b/js/components/tracking/trackingModal.js @@ -2,10 +2,10 @@ import React, { memo, useCallback, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Modal from '../common/Modal'; import { Grid, makeStyles, IconButton, Tooltip } from '@material-ui/core'; -import { Timeline, TimelineEvent } from 'react-event-timeline'; -import { Check, Clear, Close } from '@material-ui/icons'; -import palette from '../../theme/palette'; +import { Timeline } from 'react-event-timeline'; +import { Close } from '@material-ui/icons'; import { Panel } from '../common'; +import TimelineView from './timelineView'; import { setProjectTrackingActions } from '../../reducers/tracking/dispatchActions'; const useStyles = makeStyles(theme => ({ @@ -16,10 +16,7 @@ const useStyles = makeStyles(theme => ({ customContentModal: { height: '100%' }, - timelineEvent: { - borderBottom: '1px dashed ' + palette.divider, - paddingBottom: '10px' - }, + divContainer: { height: '100%', width: '100%', @@ -78,24 +75,7 @@ export const TrackingModal = memo(({ openModal, onModalClose }) => { {orderedActionList && orderedActionList.map((data, index) => { if (data && data != null) { - return ( - - ) : ( - - ) - } - iconColor={palette.primary.main} - className={classes.timelineEvent} - > - ); + return ; } })} diff --git a/js/components/userFeedback/browserApi.js b/js/components/userFeedback/browserApi.js index 3a40b14ea..f14b006eb 100644 --- a/js/components/userFeedback/browserApi.js +++ b/js/components/userFeedback/browserApi.js @@ -1,5 +1,6 @@ import { setImageSource, setIsOpenForm } from './redux/actions'; - +import { setTrackingImageSource } from '../../reducers/tracking/actions'; +import html2canvas from 'html2canvas'; /* Getting image from screen capture or */ // https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser @@ -81,3 +82,9 @@ export const captureScreen = () => async dispatch => { dispatch(setImageSource(image)); dispatch(setIsOpenForm(true)); }; + +export const captureScreenOfSnapshot = () => async dispatch => { + html2canvas(document.body).then(canvas => { + dispatch(setTrackingImageSource(canvas.toDataURL())); + }); +}; diff --git a/js/index.js b/js/index.js index fb8ddd49e..809d28816 100644 --- a/js/index.js +++ b/js/index.js @@ -5,7 +5,6 @@ import { DJANGO_CONTEXT } from './utils/djangoContext'; // Sentry logging import { init, configureScope } from '@sentry/browser'; // Setup log rocket logging -import LogRocket from 'logrocket'; import { Provider } from 'react-redux'; import { applyMiddleware, createStore } from 'redux'; import { rootReducer } from './reducers/rootReducer'; @@ -16,27 +15,6 @@ import { composeWithDevTools } from 'redux-devtools-extension'; require('react-hot-loader/patch'); -if (process.env.NODE_ENV === 'production') { - LogRocket.init('eoalzb/fragalysis'); - // This is the log rocket setup - - LogRocket.identify(DJANGO_CONTEXT['username'], { - pk: DJANGO_CONTEXT['pk'], - name: DJANGO_CONTEXT['name'], - email: DJANGO_CONTEXT['email'] - }); - - init({ - dsn: 'https://27fa0675f555431aa02ca552e93d8cfb@sentry.io/1298290' - }); - - LogRocket.getSessionURL(sessionURL => { - configureScope(scope => { - scope.setExtra('logRocketURL', sessionURL); - }); - }); -} - const middlewareEnhancer = applyMiddleware( //loggerMiddleware, thunkMiddleware, diff --git a/js/reducers/api/actions.js b/js/reducers/api/actions.js index 925e9b56a..d384e30e4 100644 --- a/js/reducers/api/actions.js +++ b/js/reducers/api/actions.js @@ -59,6 +59,14 @@ export const setMolGroupOn = function(mol_group_id) { }; }; +export const setMolGroupOff = function(mol_group_id, selectionGroups) { + return { + type: constants.SET_MOL_GROUP_OFF, + mol_group_off: mol_group_id, + selectionGroups + }; +}; + export const setMolGroupList = function(mol_group_list) { return { type: constants.SET_MOL_GROUP_LIST, diff --git a/js/reducers/api/constants.js b/js/reducers/api/constants.js index b15a46d25..00208acbc 100644 --- a/js/reducers/api/constants.js +++ b/js/reducers/api/constants.js @@ -11,6 +11,7 @@ export const constants = { SET_PANNDA_EVENT_ON: prefix + 'SET_PANNDA_EVENT_ON', SET_MOL_GROUP_ON: prefix + 'SET_MOL_GROUP_ON', + SET_MOL_GROUP_OFF: prefix + 'SET_MOL_GROUP_OFF', SET_MOL_GROUP_LIST: prefix + 'SET_MOL_GROUP_LIST', SET_MOLECULE_LIST: prefix + 'SET_MOLECULE_LIST', SET_CACHED_MOL_LISTS: prefix + 'SET_CACHED_MOL_LISTS', diff --git a/js/reducers/ngl/actions.js b/js/reducers/ngl/actions.js index e3256d0ec..233dabb06 100644 --- a/js/reducers/ngl/actions.js +++ b/js/reducers/ngl/actions.js @@ -10,34 +10,130 @@ export const deleteNglObject = target => ({ target }); -export const updateComponentRepresentation = (objectInViewID, representationID, newRepresentation, change) => ({ +export const updateComponentRepresentationVisibility = ( + objectInViewID, + representationID, + representation, + newVisibility, + skipTracking = false +) => ({ + type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY, + representationID, + representation, + newVisibility, + objectInViewID, + skipTracking +}); + +export const updateComponentRepresentationVisibilityAll = (objectInViewID, newVisibility, skipTracking = false) => ({ + type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL, + newVisibility, + objectInViewID, + skipTracking +}); + +export const updateComponentRepresentation = ( + objectInViewID, + representationID, + newRepresentation, + change, + skipTracking = false +) => ({ type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION, representationID, newRepresentation, objectInViewID, - change + change, + skipTracking }); -export const addComponentRepresentation = (objectInViewID, newRepresentation) => ({ +export const addComponentRepresentation = (objectInViewID, newRepresentation, skipTracking = false) => ({ type: CONSTANTS.ADD_COMPONENT_REPRESENTATION, newRepresentation, - objectInViewID + objectInViewID, + skipTracking }); -export const removeComponentRepresentation = (objectInViewID, representation) => ({ +export const removeComponentRepresentation = (objectInViewID, representation, skipTracking = false) => ({ type: CONSTANTS.REMOVE_COMPONENT_REPRESENTATION, representation, + objectInViewID, + skipTracking +}); + +export const changeComponentRepresentation = (objectInViewID, oldRepresentation, newRepresentation) => ({ + type: CONSTANTS.CHANGE_COMPONENT_REPRESENTATION, + oldRepresentation, + newRepresentation, objectInViewID }); -export const setNglViewParams = (key, value, stage = undefined) => { +export const setNglViewParams = (key, value, stage = undefined, objectId = undefined) => { if (stage) { stage.setParameters({ [key]: value }); } return { type: CONSTANTS.SET_NGL_VIEW_PARAMS, key, - value + value, + object_id: objectId + }; +}; + +export const setBackgroundColor = color => { + return { + type: CONSTANTS.SET_BACKGROUND_COLOR, + payload: color + }; +}; + +export const setNglClipNearAction = (newValue, oldValue) => { + return { + type: CONSTANTS.SET_CLIP_NEAR, + payload: { + newValue: newValue, + oldValue: oldValue + } + }; +}; + +export const setNglClipFarAction = (newValue, oldValue) => { + return { + type: CONSTANTS.SET_CLIP_FAR, + payload: { + newValue: newValue, + oldValue: oldValue + } + }; +}; + +export const setNglClipDistAction = (newValue, oldValue) => { + return { + type: CONSTANTS.SET_CLIP_DIST, + payload: { + newValue: newValue, + oldValue: oldValue + } + }; +}; + +export const setNglFogNearAction = (newValue, oldValue) => { + return { + type: CONSTANTS.SET_FOG_NEAR, + payload: { + newValue: newValue, + oldValue: oldValue + } + }; +}; + +export const setNglFogFarAction = (newValue, oldValue) => { + return { + type: CONSTANTS.SET_FOG_FAR, + payload: { + newValue: newValue, + oldValue: oldValue + } }; }; @@ -86,3 +182,8 @@ export const removeMoleculeOrientation = moleculeGroupID => ({ type: CONSTANTS.REMOVE_MOLECULE_ORIENTATION, payload: moleculeGroupID }); + +export const addToPdbCache = (name, cacheItem) => ({ + type: CONSTANTS.ADD_TO_PDB_CACHE, + payload: { name: name, cacheItem: cacheItem } +}); diff --git a/js/reducers/ngl/actions.test.js b/js/reducers/ngl/actions.test.js index d862ef084..bf7e6ac9a 100644 --- a/js/reducers/ngl/actions.test.js +++ b/js/reducers/ngl/actions.test.js @@ -297,35 +297,6 @@ describe("testing ngl reducer's actions", () => { expect(result.nglOrientations).toStrictEqual(defaultScene[SCENES.defaultScene].nglOrientations); }); - /* - it('should reset current state to session scene', () => { - expect.hasAssertions(); - const sessionScene = { - objectsInView: { - molecule_1: { - properties: [{ a: 'dfg' }], - representations: [ - { - lastKnownID: 78904, - uuid: 9903, - other: 'fdjsdj' - }, - { - lastKnownID: 178904, - uuid: 19903, - other: '1fdjsdj' - } - ] - } - }, - nglOrientations: { a: [24, 566] } - }; - - let result = nglReducers(initialState, actions.setNglStateFromCurrentSnapshot(sessionScene)); - expect(result.objectsInView).toStrictEqual(sessionScene.objectsInView); - expect(result.nglOrientations).toStrictEqual(sessionScene.nglOrientations); - }); -*/ it('should remove all ngl view components', () => { expect.hasAssertions(); let result = nglReducers(initialState, actions.removeAllNglComponents()); diff --git a/js/reducers/ngl/constants.js b/js/reducers/ngl/constants.js index 78ca7400b..f35064079 100644 --- a/js/reducers/ngl/constants.js +++ b/js/reducers/ngl/constants.js @@ -4,9 +4,12 @@ export const CONSTANTS = { LOAD_OBJECT: prefix + 'LOAD_OBJECT', DELETE_OBJECT: prefix + 'DELETE_OBJECT', // NGL Component Representation + UPDATE_COMPONENT_REPRESENTATION_VISIBILITY: prefix + 'UPDATE_COMPONENT_REPRESENTATION_VISIBILITY', + UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL: prefix + 'UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL', UPDATE_COMPONENT_REPRESENTATION: prefix + 'UPDATE_COMPONENT_REPRESENTATION', REMOVE_COMPONENT_REPRESENTATION: prefix + 'REMOVE_COMPONENT_REPRESENTATION', ADD_COMPONENT_REPRESENTATION: prefix + 'ADD_COMPONENT_REPRESENTATION', + CHANGE_COMPONENT_REPRESENTATION: prefix + 'CHANGE_COMPONENT_REPRESENTATION', SET_NGL_VIEW_PARAMS: prefix + 'SET_NGL_VIEW_PARAMS', SET_ORIENTATION: prefix + 'SET_ORIENTATION', @@ -22,7 +25,16 @@ export const CONSTANTS = { SET_MOLECULE_ORIENTATIONS: prefix + 'SET_MOLECULE_ORIENTATIONS', APPEND_MOLECULE_ORIENTATION: prefix + 'SET_MOLECULE_ORIENTATION', - REMOVE_MOLECULE_ORIENTATION: prefix + 'REMOVE_MOLECULE_ORIENTATION' + REMOVE_MOLECULE_ORIENTATION: prefix + 'REMOVE_MOLECULE_ORIENTATION', + + ADD_TO_PDB_CACHE: prefix + 'ADD_TO_PDB_CACHE', + + SET_BACKGROUND_COLOR: prefix + 'SET_BACKGROUND_COLOR', + SET_CLIP_NEAR: prefix + 'SET_CLIP_NEAR', + SET_CLIP_FAR: prefix + 'SET_CLIP_FAR', + SET_CLIP_DIST: prefix + 'SET_CLIP_DIST', + SET_FOG_NEAR: prefix + 'SET_FOG_NEAR', + SET_FOG_FAR: prefix + 'SET_FOG_FAR' }; export const SCENES = { diff --git a/js/reducers/ngl/dispatchActions.js b/js/reducers/ngl/dispatchActions.js index 770dcde77..f6677442c 100644 --- a/js/reducers/ngl/dispatchActions.js +++ b/js/reducers/ngl/dispatchActions.js @@ -7,7 +7,13 @@ import { setNglStateFromCurrentSnapshot, setMoleculeOrientations, setNglOrientation, - setNglViewParams + setNglViewParams, + setBackgroundColor, + setNglClipNearAction, + setNglClipFarAction, + setNglClipDistAction, + setNglFogNearAction, + setNglFogFarAction } from './actions'; import { isEmpty, isEqual } from 'lodash'; import { createRepresentationsArray } from '../../components/nglView/generatingObjects'; @@ -23,6 +29,7 @@ import { import { nglObjectDictionary } from '../../components/nglView/renderingObjects'; import { createInitialSnapshot } from '../../components/snapshot/redux/dispatchActions'; import { VIEWS } from '../../constants/constants'; +import { NGL_PARAMS } from '../../components/nglView/constants/index'; export const loadObject = ({ target, @@ -45,7 +52,8 @@ export const loadObject = ({ object_name: versionFixedTarget.name, representations: previousRepresentations, orientationMatrix, - markAsRightSideLigand + markAsRightSideLigand, + dispatch }) .then(representations => { dispatch(loadNglObject(versionFixedTarget, representations)) @@ -156,7 +164,7 @@ export const reloadNglViewFromSnapshot = (stage, display_div, snapshot) => (disp if (display_div !== VIEWS.SUMMARY_VIEW) { // loop over nglViewParams Object.keys(snapshot.viewParams).forEach(param => { - dispatch(setNglViewParams(param, snapshot.viewParams[param], stage)); + dispatch(setNglViewParams(param, snapshot.viewParams[param], stage, VIEWS.MAJOR_VIEW)); }); // nglOrientations @@ -172,3 +180,34 @@ export const reloadNglViewFromSnapshot = (stage, display_div, snapshot) => (disp } }); }; + +export const setNglBckGrndColor = (color, major, summary) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.backgroundColor, color, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglViewParams(NGL_PARAMS.backgroundColor, color, summary, VIEWS.SUMMARY_VIEW)); + dispatch(setBackgroundColor(color)); +}; + +export const setNglClipNear = (newValue, oldValue, major) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.clipNear, newValue, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglClipNearAction(newValue, oldValue)); +}; + +export const setNglClipFar = (newValue, oldValue, major) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.clipFar, newValue, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglClipFarAction(newValue, oldValue)); +}; + +export const setNglClipDist = (newValue, oldValue, major) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.clipDist, newValue, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglClipDistAction(newValue, oldValue)); +}; + +export const setNglFogNear = (newValue, oldValue, major) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.fogNear, newValue, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglFogNearAction(newValue, oldValue)); +}; + +export const setNglFogFar = (newValue, oldValue, major) => (dispatch, getState) => { + dispatch(setNglViewParams(NGL_PARAMS.fogFar, newValue, major, VIEWS.MAJOR_VIEW)); + dispatch(setNglFogFarAction(newValue, oldValue)); +}; \ No newline at end of file diff --git a/js/reducers/ngl/nglReducers.js b/js/reducers/ngl/nglReducers.js index a187a370b..dd528df9d 100644 --- a/js/reducers/ngl/nglReducers.js +++ b/js/reducers/ngl/nglReducers.js @@ -1,6 +1,5 @@ import { BACKGROUND_COLOR, NGL_PARAMS } from '../../components/nglView/constants'; import { CONSTANTS } from './constants'; -import NglView from '../../components/nglView/nglView'; import { VIEWS } from '../../constants/constants'; export const INITIAL_STATE = { @@ -39,7 +38,8 @@ export const INITIAL_STATE = { [VIEWS.MAJOR_VIEW]: 0, [VIEWS.SUMMARY_VIEW]: 0 }, - moleculeOrientations: {} + moleculeOrientations: {}, + pdbCache: {} }; export default function nglReducers(state = INITIAL_STATE, action = {}) { @@ -174,6 +174,11 @@ export default function nglReducers(state = INITIAL_STATE, action = {}) { } return Object.assign({}, state, { moleculeOrientations: diminishedMoleculeOrientations }); + case CONSTANTS.ADD_TO_PDB_CACHE: + return {...state, pdbCache: { + ...state.pdbCache, [action.payload.name]: action.payload.cacheItem + }}; + default: return state; } diff --git a/js/reducers/selection/actions.js b/js/reducers/selection/actions.js index 9c154498c..9055a1112 100644 --- a/js/reducers/selection/actions.js +++ b/js/reducers/selection/actions.js @@ -11,17 +11,35 @@ export const setToBuyList = function(to_buy_list) { }; }; -export const appendToBuyList = function(item) { +export const appendToBuyList = function(item, index, skipTracking = false) { return { type: constants.APPEND_TO_BUY_LIST, - item: item + item: item, + index: index, + skipTracking: skipTracking }; }; -export const removeFromToBuyList = function(item) { +export const removeFromToBuyList = function(item, index, skipTracking = false) { return { type: constants.REMOVE_FROM_TO_BUY_LIST, - item: item + item: item, + index: index, + skipTracking: skipTracking + }; +}; + +export const appendToBuyListAll = function(items) { + return { + type: constants.APPEND_TO_BUY_LIST_ALL, + items: items + }; +}; + +export const removeFromToBuyListAll = function(items) { + return { + type: constants.REMOVE_FROM_BUY_LIST_ALL, + items: items }; }; @@ -39,10 +57,11 @@ export const setCurrentVector = vectorSmile => { }; }; -export const setFragmentDisplayList = function(fragmentDisplayList) { +export const setFragmentDisplayList = function(fragmentDisplayList, skipTracking = false) { return { type: constants.SET_FRAGMENT_DISPLAY_LIST, - fragmentDisplayList: fragmentDisplayList + fragmentDisplayList: fragmentDisplayList, + skipTracking }; }; @@ -62,10 +81,11 @@ export const removeFromFragmentDisplayList = function(item, skipTracking = false }; }; -export const setProteinList = function(proteinList) { +export const setProteinList = function(proteinList, skipTracking = false) { return { type: constants.SET_PROTEIN_LIST, - proteinList: proteinList + proteinList: proteinList, + skipTracking }; }; @@ -84,10 +104,11 @@ export const removeFromProteinList = function(item, skipTracking = false) { skipTracking: skipTracking }; }; -export const setComplexList = function(complexList) { +export const setComplexList = function(complexList, skipTracking = false) { return { type: constants.SET_COMPLEX_LIST, - complexList: complexList + complexList: complexList, + skipTracking }; }; @@ -107,10 +128,11 @@ export const removeFromComplexList = function(item, skipTracking = false) { }; }; -export const setSurfaceList = function(surfaceList) { +export const setSurfaceList = function(surfaceList, skipTracking = false) { return { type: constants.SET_SURFACE_LIST, - surfaceList: surfaceList + surfaceList: surfaceList, + skipTracking }; }; @@ -151,10 +173,11 @@ export const removeFromDensityList = function(item) { }; }; -export const setVectorOnList = function(vectorOnList) { +export const setVectorOnList = function(vectorOnList, skipTracking = false) { return { type: constants.SET_VECTOR_ON_LIST, - vectorOnList: vectorOnList + vectorOnList: vectorOnList, + skipTracking }; }; diff --git a/js/reducers/selection/constants.js b/js/reducers/selection/constants.js index 24269ecef..d91a8af26 100644 --- a/js/reducers/selection/constants.js +++ b/js/reducers/selection/constants.js @@ -7,7 +7,9 @@ export const constants = { SET_TO_BUY_LIST: prefix + 'SET_TO_BUY_LIST', APPEND_TO_BUY_LIST: prefix + 'APPEND_TO_BUY_LIST', + APPEND_TO_BUY_LIST_ALL: prefix + 'APPEND_TO_BUY_LIST_ALL', REMOVE_FROM_TO_BUY_LIST: prefix + 'REMOVE_FROM_TO_BUY_LIST', + REMOVE_FROM_BUY_LIST_ALL: prefix + 'REMOVE_FROM_BUY_LIST_ALL', SET_VECTOR_LIST: prefix + 'SET_VECTOR_LIST', SET_CURRENT_VECTOR: prefix + 'SET_CURRENT_VECTOR', SET_FRAGMENT_DISPLAY_LIST: prefix + 'SET_FRAGMENT_DISPLAY_LIST', diff --git a/js/reducers/selection/dispatchActions.js b/js/reducers/selection/dispatchActions.js index 29560a7ee..269ec78d8 100644 --- a/js/reducers/selection/dispatchActions.js +++ b/js/reducers/selection/dispatchActions.js @@ -4,8 +4,13 @@ import { getAllCompoundsList } from './selectors'; import { MOL_ATTRIBUTES } from '../../components/preview/molecule/redux/constants'; export const selectVectorAndResetCompounds = vectorSmile => async (dispatch, getState) => { + const state = getState(); + let currentVector = state.selectionReducers.currentVector; + if (currentVector !== vectorSmile) { + await dispatch(setCurrentVector(vectorSmile)); + } + await dispatch(resetCurrentCompoundsSettings(false)); - await dispatch(setCurrentVector(vectorSmile)); const currentCompoundsList = getAllCompoundsList(getState()); dispatch(setCurrentCompounds(currentCompoundsList)); }; diff --git a/js/reducers/tracking/actions.js b/js/reducers/tracking/actions.js index d3efec7a2..33d0e82c4 100644 --- a/js/reducers/tracking/actions.js +++ b/js/reducers/tracking/actions.js @@ -21,6 +21,13 @@ export const appendToUndoRedoActionList = function(track_action) { }; }; +export const setUndoRedoActionList = (undo_redo_actions_list) => { + return { + type: constants.SET_UNDO_REDO_ACTIONS_LIST, + undo_redo_actions_list: undo_redo_actions_list + }; +} + export const setCurrentActionsList = function(current_actions_list) { return { type: constants.SET_CURRENT_ACTIONS_LIST, @@ -84,6 +91,13 @@ export const setProjectActionList = function(project_actions_list) { }; }; +export const setSnapshotImageActionList = function(snapshotActionImageList) { + return { + type: constants.SET_SNAPSOT_IMAGE_ACTIONS_LIST, + snapshotActionImageList: snapshotActionImageList + }; +}; + export const setIsActionsSaving = function(isActionSaving) { return { type: constants.SET_IS_ACTIONS_SAVING, @@ -99,8 +113,20 @@ export const setIsActionsRestoring = function(isActionRestoring, isActionRestore }; }; +export const setIsActionTracking = function(isActionTracking) { + return { + type: constants.SET_IS_ACTION_TRACKING, + isActionTracking: isActionTracking + }; +}; + export const resetTrackingState = function() { return { type: constants.RESET_TRACKING_STATE }; }; + +export const setTrackingImageSource = imageSource => ({ + type: constants.SET_TRACKING_IMAGE_SOURCE, + payload: imageSource +}); diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index b1576102b..7f2183f77 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -3,7 +3,6 @@ const prefix = 'REDUCERS_TRACKING_'; export const constants = { SET_ACTIONS_LIST: prefix + 'SET_ACTIONS_LIST', APPEND_ACTIONS_LIST: prefix + 'APPEND_ACTIONS_LIST', - APPEND_UNDO_REDO_ACTIONS_LIST: prefix + 'APPEND_UNDO_REDO_ACTIONS_LIST', SET_CURRENT_ACTIONS_LIST: prefix + 'SET_CURRENT_ACTIONS_LIST', SET_IS_TRACKING_COMPOUNDS_RESTORING: prefix + 'SET_IS_TRACKING_COMPOUNDS_RESTORING', SET_IS_TRACKING_MOLECULES_RESTORING: prefix + 'SET_IS_TRACKING_MOLECULES_RESTORING', @@ -15,7 +14,12 @@ export const constants = { SET_PROJECT_ACTIONS_LIST: prefix + 'SET_PROJECT_ACTIONS_LIST', SET_IS_ACTIONS_SAVING: prefix + 'SET_IS_ACTIONS_SAVING', SET_IS_ACTIONS_RESTORING: prefix + 'SET_IS_ACTIONS_RESTORING', - RESET_TRACKING_STATE: prefix + 'RESET_TRACKING_STATE' + SET_IS_ACTION_TRACKING: prefix + 'SET_IS_ACTION_TRACKING', + RESET_TRACKING_STATE: prefix + 'RESET_TRACKING_STATE', + SET_TRACKING_IMAGE_SOURCE: prefix + 'SET_TRACKING_IMAGE_SOURCE', + SET_SNAPSOT_IMAGE_ACTIONS_LIST: prefix + 'SET_SNAPSOT_IMAGE_ACTIONS_LIST', + APPEND_UNDO_REDO_ACTIONS_LIST: prefix + 'APPEND_UNDO_REDO_ACTIONS_LIST', + SET_UNDO_REDO_ACTIONS_LIST: prefix + 'SET_UNDO_REDO_ACTIONS_LIST' }; export const actionType = { @@ -32,23 +36,39 @@ export const actionType = { SURFACE_TURNED_OFF: 'SURFACE_TURNED_OFF', VECTORS_TURNED_ON: 'VECTORS_TURNED_ON', VECTORS_TURNED_OFF: 'VECTORS_TURNED_OFF', + CLASS_SELECTED: 'CLASS_SELECTED', + CLASS_UPDATED: 'CLASS_UPDATED', VECTOR_SELECTED: 'VECTOR_SELECTED', VECTOR_DESELECTED: 'VECTOR_DESELECTED', MOLECULE_ADDED_TO_SHOPPING_CART: 'MOLECULE_ADDED_TO_SHOPPING_CART', + MOLECULE_ADDED_TO_SHOPPING_CART_ALL: 'MOLECULE_ADDED_TO_SHOPPING_CART_ALL', MOLECULE_REMOVED_FROM_SHOPPING_CART: 'MOLECULE_REMOVED_FROM_SHOPPING_CART', + MOLECULE_REMOVED_FROM_SHOPPING_CART_ALL: 'MOLECULE_REMOVED_FROM_SHOPPING_CART_ALL', + VECTOR_COUMPOUND_ADDED: 'VECTOR_COUMPOUND_ADDED', + VECTOR_COUMPOUND_REMOVED: 'VECTOR_COUMPOUND_REMOVED', COMPOUND_SELECTED: 'COMPOUND_SELECTED', COMPOUND_DESELECTED: 'COMPOUND_DESELECTED', - REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED', + REPRESENTATION_UPDATED: 'REPRESENTATION_UPDATED', + REPRESENTATION_VISIBILITY_UPDATED: 'REPRESENTATION_VISIBILITY_UPDATED', + REPRESENTATION_VISIBILITY_ALL_UPDATED: 'REPRESENTATION_VISIBILITY_ALL_UPDATED', REPRESENTATION_ADDED: 'REPRESENTATION_ADDED', REPRESENTATION_REMOVED: 'REPRESENTATION_REMOVED', + REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED', NGL_STATE: 'NGL_STATE', UNDO: 'UNDO', REDO: 'REDO', + SNAPSHOT: 'SNAPSHOT', ALL_HIDE: 'ALL_HIDE', ALL_TURNED_ON: 'ALL_TURNED_ON', ALL_TURNED_OFF: 'ALL_TURNED_OFF', ALL_TURNED_ON_BY_TYPE: 'ALL_TURNED_ON_BY_TYPE', - ALL_TURNED_OFF_BY_TYPE: 'ALL_TURNED_OFF_BY_TYPE' + ALL_TURNED_OFF_BY_TYPE: 'ALL_TURNED_OFF_BY_TYPE', + BACKGROUND_COLOR_CHANGED: 'BACKGROUND_COLOR_CHANGED', + CLIP_NEAR: 'CLIP_NEAR', + CLIP_FAR: 'CLIP_FAR', + CLIP_DIST: 'CLIP_DIST', + FOG_NEAR: 'FOG_NEAR', + FOG_FAR: 'FOG_FAR' }; export const actionDescription = { @@ -58,23 +78,27 @@ export const actionDescription = { SELECTED: 'was selected', DESELECTED: 'was deselected', HIDDEN: 'hidden', + VISIBLE: 'visible', CANCELED: 'canceled', ADDED: 'was added', REMOVED: 'was removed', CHANGED: 'was changed', + UPDATED: 'was updated', + VISIBILITY: 'visiblity', TO_SHOPPING_CART: 'to shopping cart', FROM_SHOPPING_CART: 'from shopping cart', LIGAND: 'Ligand', SIDECHAIN: 'Sidechain', INTERACTION: 'Interaction', VECTOR: 'Vector', + COMPOUND: 'Compound', + CLASS: 'Compound colour', SURFACE: 'Surface', SITE: 'Site', TARGET: 'Target', ALL: 'All', LIGANDS: 'Ligands', - SIDECHAINS: 'Sidechains', - INTERACTIONS: 'Interactions' + SIDECHAINS: 'Sidechains' }; export const actionObjectType = { @@ -84,5 +108,16 @@ export const actionObjectType = { COMPOUND: 'COMPOUND', INSPIRATION: 'INSPIRATION', CROSS_REFERENCE: 'CROSS_REFERENCE', - REPRESENTATION: 'REPRESENTATION' + REPRESENTATION: 'REPRESENTATION', + VIEWER_SETTINGS: 'VIEWER_SETTINGS' }; + +export const actionAnnotation = { + CHECK: 'CHECK', + CLEAR: 'CLEAR', + WARNING: 'WARNING', + FAVORITE: 'FAVORITE', + STAR: 'STAR' +}; + +export const NUM_OF_SECONDS_TO_IGNORE_MERGE = 5; diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index 93b2b353e..f0c784964 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -5,9 +5,9 @@ import { setIsUndoRedoAction } from './actions'; import { createInitAction } from './trackingActions'; -import { actionType, actionObjectType } from './constants'; +import { actionType, actionObjectType, NUM_OF_SECONDS_TO_IGNORE_MERGE } from './constants'; import { VIEWS } from '../../../js/constants/constants'; -import { setCurrentVector, appendToBuyList, removeFromToBuyList, setHideAll } from '../selection/actions'; +import { setCurrentVector, appendToBuyList, setHideAll } from '../selection/actions'; import { resetReducersForRestoringActions, shouldLoadProtein, @@ -33,6 +33,12 @@ import { removeSurface, removeVector } from '../../components/preview/molecule/redux/dispatchActions'; +import { + handleBuyList, + handleBuyListAll, + handleShowVectorCompound +} from '../../components/preview/compounds/redux/dispatchActions'; +import { setCurrentCompoundClass, setCompoundClasses } from '../../components/preview/compounds/redux/actions'; import { colourList } from '../../components/preview/molecule/moleculeView'; import { addDatasetComplex, @@ -56,24 +62,42 @@ import { getUrl, loadAllMolsFromMolGroup } from '../../../js/utils/genericList'; import { removeComponentRepresentation, addComponentRepresentation, - updateComponentRepresentation + updateComponentRepresentation, + updateComponentRepresentationVisibility, + updateComponentRepresentationVisibilityAll, + changeComponentRepresentation } from '../../../js/reducers/ngl/actions'; import * as listType from '../../constants/listTypes'; import { assignRepresentationToComp } from '../../components/nglView/generatingObjects'; -import { deleteObject, setOrientation } from '../../../js/reducers/ngl/dispatchActions'; -import { setSendActionsList, setIsActionsSending, setIsActionsLoading, setActionsList } from './actions'; +import { + deleteObject, + setOrientation, + setNglBckGrndColor, + setNglClipNear, + setNglClipFar, + setNglClipDist, + setNglFogNear, + setNglFogFar +} from '../../../js/reducers/ngl/dispatchActions'; +import { + setSendActionsList, + setIsActionsSending, + setIsActionsLoading, + setActionsList, + setSnapshotImageActionList, + setUndoRedoActionList +} from './actions'; import { api, METHOD } from '../../../js/utils/api'; import { base_url } from '../../components/routes/constants'; import { CONSTANTS } from '../../../js/constants/constants'; import moment from 'moment'; import { - appendToActionList, appendToSendActionList, setProjectActionList, setIsActionsSaving, setIsActionsRestoring, - appendToUndoRedoActionList, - resetTrackingState + resetTrackingState, + setIsActionTracking } from './actions'; import { setSelectedAll, @@ -87,13 +111,28 @@ import { setSelectedAllByType as setSelectedAllByTypeOfDataset, setDeselectedAllByType as setDeselectedAllByTypeOfDataset } from '../../components/datasets/redux/actions'; +import { selectVectorAndResetCompounds } from '../../../js/reducers/selection/dispatchActions'; + +export const addCurrentActionsListToSnapshot = (snapshot, project, nglViewList) => async (dispatch, getState) => { + let projectID = project && project.projectID; + let actionList = await dispatch(getTrackingActions(projectID)); -export const saveCurrentActionsList = (snapshotID, projectID, nglViewList) => async (dispatch, getState) => { + await dispatch(setSnapshotToActions(actionList, snapshot, projectID, project, nglViewList, true)); +}; + +export const saveCurrentActionsList = (snapshot, project, nglViewList, all = false) => async (dispatch, getState) => { + let projectID = project && project.projectID; let actionList = await dispatch(getTrackingActions(projectID)); - dispatch(saveActionsList(snapshotID, actionList, nglViewList)); + + if (all === false) { + dispatch(setSnapshotToActions(actionList, snapshot, projectID, project, nglViewList, false)); + } else { + dispatch(setSnapshotToAllActions(actionList, snapshot, projectID)); + } + await dispatch(saveActionsList(project, snapshot, actionList, nglViewList)); }; -export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatch, getState) => { +const saveActionsList = (project, snapshot, actionList, nglViewList) => async (dispatch, getState) => { const state = getState(); const currentTargetOn = state.apiReducers.target_on; @@ -101,23 +140,14 @@ export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatc const currentLigands = state.selectionReducers.fragmentDisplayList; const currentProteins = state.selectionReducers.proteinList; const currentComplexes = state.selectionReducers.complexList; - const currentSurfaces = state.selectionReducers.surfaceList; - const currentVectors = state.selectionReducers.vectorOnList; - const currentBuyList = state.selectionReducers.to_buy_list; - const currentVector = state.selectionReducers.currentVector; const currentSelectionAll = state.selectionReducers.moleculeAllSelection; const currentDatasetLigands = state.datasetsReducers.ligandLists; const currentDatasetProteins = state.datasetsReducers.proteinLists; const currentDatasetComplexes = state.datasetsReducers.complexLists; - const currentDatasetSurfaces = state.datasetsReducers.surfaceLists; const currentDatasetSelectionAll = state.datasetsReducers.moleculeAllSelection; - const currentDatasetBuyList = state.datasetsReducers.compoundsToBuyDatasetMap; - const currentobjectsInView = state.nglReducers.objectsInView; - const currentTargets = (currentTargetOn && [currentTargetOn]) || []; - const currentVectorSmiles = (currentVector && [currentVector]) || []; let orderedActionList = actionList.reverse((a, b) => a.timestamp - b.timestamp); @@ -201,97 +231,202 @@ export const saveActionsList = (snapshotID, actionList, nglViewList) => (dispatc getCollection(currentProteins), currentActions ); + const snapshotID = snapshot && snapshot.id; + if (snapshotID) { + 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.complexList; + const currentSurfaces = state.selectionReducers.surfaceList; + const currentVectors = state.selectionReducers.vectorOnList; + const currentBuyList = state.selectionReducers.to_buy_list; + const currentVector = state.selectionReducers.currentVector; + const currentSelectionAll = state.selectionReducers.moleculeAllSelection; + + const currentDatasetLigands = state.datasetsReducers.ligandLists; + const currentDatasetProteins = state.datasetsReducers.proteinLists; + const currentDatasetComplexes = state.datasetsReducers.complexLists; + const currentDatasetSurfaces = state.datasetsReducers.surfaceLists; + const currentDatasetSelectionAll = state.datasetsReducers.moleculeAllSelection; + + const currentDatasetBuyList = state.datasetsReducers.compoundsToBuyDatasetMap; + const currentobjectsInView = state.nglReducers.objectsInView; + + const currentTargets = (currentTargetOn && [currentTargetOn]) || []; + const currentVectorSmiles = (currentVector && [currentVector]) || []; + + let orderedActionList = actionList.reverse((a, b) => a.timestamp - b.timestamp); + + 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.ALL_TURNED_ON, + getCollection(currentSelectionAll), + currentActions + ); + getCurrentActionList( + orderedActionList, + actionType.ALL_TURNED_ON, + getCollectionOfDataset(currentDatasetSelectionAll), + 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.SIDECHAINS_TURNED_ON, + getCollection(currentProteins), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.MOLECULE_ADDED_TO_SHOPPING_CART, - getCollectionOfShoppingCart(currentBuyList), - 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.LIGAND_TURNED_ON, - getCollectionOfDataset(currentDatasetLigands), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.MOLECULE_ADDED_TO_SHOPPING_CART, + getCollectionOfShoppingCart(currentBuyList), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.SIDECHAINS_TURNED_ON, - getCollectionOfDataset(currentDatasetProteins), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.LIGAND_TURNED_ON, + getCollectionOfDataset(currentDatasetLigands), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.INTERACTIONS_TURNED_ON, - getCollectionOfDataset(currentDatasetComplexes), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.SIDECHAINS_TURNED_ON, + getCollectionOfDataset(currentDatasetProteins), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.SURFACE_TURNED_ON, - getCollectionOfDataset(currentDatasetSurfaces), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.INTERACTIONS_TURNED_ON, + getCollectionOfDataset(currentDatasetComplexes), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.COMPOUND_SELECTED, - getCollectionOfDataset(currentDatasetBuyList), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.SURFACE_TURNED_ON, + getCollectionOfDataset(currentDatasetSurfaces), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.REPRESENTATION_ADDED, - getCollectionOfDatasetOfRepresentation(currentobjectsInView), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.COMPOUND_SELECTED, + getCollectionOfDataset(currentDatasetBuyList), + currentActions + ); - getCurrentActionList( - orderedActionList, - actionType.REPRESENTATION_CHANGED, - getCollectionOfDatasetOfRepresentation(currentobjectsInView), - currentActions - ); + getCurrentActionList( + orderedActionList, + actionType.REPRESENTATION_ADDED, + getCollectionOfDatasetOfRepresentation(currentobjectsInView), + currentActions + ); - if (nglViewList) { - let nglStateList = nglViewList.map(nglView => { - return { id: nglView.id, orientation: nglView.stage.viewerControls.getOrientation() }; - }); + getCurrentActionList( + orderedActionList, + actionType.REPRESENTATION_UPDATED, + getCollectionOfDatasetOfRepresentation(currentobjectsInView), + currentActions + ); - let trackAction = { - type: actionType.NGL_STATE, - timestamp: Date.now(), - nglStateList: nglStateList - }; + if (nglViewList) { + let nglStateList = nglViewList.map(nglView => { + return { id: nglView.id, orientation: nglView.stage.viewerControls.getOrientation() }; + }); + + let trackAction = { + type: actionType.NGL_STATE, + timestamp: Date.now(), + nglStateList: nglStateList + }; + + currentActions.push(Object.assign({ ...trackAction })); + } - currentActions.push(Object.assign({ ...trackAction })); + await dispatch(saveSnapshotAction(snapshot, project, currentActions)); + await dispatch(saveTrackingActions(currentActions, snapshotID)); + dispatch(setCurrentActionsList(currentActions)); } +}; - dispatch(setCurrentActionsList(currentActions)); - dispatch(saveTrackingActions(currentActions, snapshotID)); +const saveSnapshotAction = (snapshot, project, currentActions) => async (dispatch, getState) => { + const state = getState(); + const trackingImageSource = state.trackingReducers.trackingImageSource; + + let sendActions = []; + let snapshotAction = { + type: actionType.SNAPSHOT, + timestamp: Date.now(), + object_name: snapshot.title, + object_id: snapshot.id, + snapshotId: snapshot.id, + text: `Snapshot: ${snapshot.id} - ${snapshot.title}`, + image: trackingImageSource + }; + sendActions.push(snapshotAction); + currentActions.push(snapshotAction); + await dispatch(sendTrackingActions(sendActions, project)); }; -export const saveTrackingActions = (currentActions, snapshotID) => (dispatch, getState) => { +const setSnapshotToActions = (actionList, snapshot, projectID, project, nglViewList, addToSnapshot) => async ( + dispatch, + getState +) => { + if (actionList && snapshot) { + let actionsWithoutSnapshot = actionList.filter(a => a.snapshotId === null || a.snapshotId === undefined); + let updatedActions = actionsWithoutSnapshot.map(obj => ({ ...obj, snapshotId: snapshot.id })); + dispatch(setAndUpdateTrackingActions(updatedActions, projectID)); + + if (addToSnapshot === true) { + await dispatch(saveActionsList(project, snapshot, updatedActions, nglViewList)); + } + } +}; + +const setSnapshotToAllActions = (actionList, snapshot, projectID) => async (dispatch, getState) => { + if (actionList && snapshot) { + let updatedActions = actionList.map(obj => ({ ...obj, snapshotId: snapshot.id })); + dispatch(setAndUpdateTrackingActions(updatedActions, projectID)); + } +}; + +export const saveTrackingActions = (currentActions, snapshotID) => async (dispatch, getState) => { const state = getState(); const project = state.projectReducers.currentProject; const projectID = project && project.projectID; @@ -578,13 +713,15 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, await dispatch(restoreActions(orderedActionList, majorView.stage)); await dispatch(restoreRepresentationActions(orderedActionList, stages)); await dispatch(restoreProject(projectId)); - await dispatch(restoreNglStateAction(orderedActionList, stages)); + dispatch(restoreSnapshotImageActions(projectId)); + dispatch(restoreNglStateAction(orderedActionList, stages)); dispatch(setIsActionsRestoring(false, true)); } }; const restoreNglStateAction = (orderedActionList, stages) => (dispatch, getState) => { - let action = orderedActionList.find(action => action.type === actionType.NGL_STATE); + let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); + let action = [...actions].pop(); if (action && action.nglStateList) { action.nglStateList.forEach(nglView => { dispatch(setOrientation(nglView.id, nglView.orientation)); @@ -742,8 +879,6 @@ const restoreAllSelectionActions = (moleculesAction, stage, isSelection) => (dis }; const restoreAllSelectionByTypeActions = (moleculesAction, stage, isSelection) => (dispatch, getState) => { - let state = getState(); - let actions = isSelection === true ? moleculesAction.filter( @@ -816,12 +951,26 @@ const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, get } let representationsChangesActions = moleculesAction.filter( - action => action.type === actionType.REPRESENTATION_CHANGED + action => action.type === actionType.REPRESENTATION_UPDATED ); if (representationsChangesActions) { representationsChangesActions.forEach(action => { - dispatch(changeRepresentation(true, action.change, action.object_id, action.representation, nglView)); + dispatch(updateRepresentation(true, action.change, action.object_id, action.representation, nglView)); + }); + } +}; + +const restoreSnapshotImageActions = projectID => async (dispatch, getState) => { + let actionList = await dispatch(getTrackingActions(projectID)); + + let snapshotActions = actionList.filter(action => action.type === actionType.SNAPSHOT); + if (snapshotActions) { + let actions = snapshotActions.map(s => { + return { id: s.object_id, image: s.image, title: s.object_name, timestamp: s.timestamp }; }); + const key = 'object_id'; + const arrayUniqueByKey = [...new Map(actions.map(item => [item[key], item])).values()]; + dispatch(setSnapshotImageActionList(arrayUniqueByKey)); } }; @@ -989,36 +1138,80 @@ const getCompound = (action, state) => { }; export const undoAction = (stages = []) => (dispatch, getState) => { - const state = getState(); - let action = null; - dispatch(setIsUndoRedoAction(true)); + let action = dispatch(getUndoAction()); + if (action) { + Promise.resolve(dispatch(handleUndoAction(action, stages))).then(() => { + dispatch(setIsUndoRedoAction(false)); + }); + } +}; +const getUndoAction = () => (dispatch, getState) => { + const state = getState(); const actionUndoList = state.undoableTrackingReducers.future; + + let action = { text: '' }; let actions = actionUndoList && actionUndoList[0]; if (actions) { let actionsLenght = actions.undo_redo_actions_list.length; actionsLenght = actionsLenght > 0 ? actionsLenght - 1 : actionsLenght; action = actions.undo_redo_actions_list[actionsLenght]; - - Promise.resolve(dispatch(handleUndoAction(action, stages))).then(() => { - dispatch(setIsUndoRedoAction(false)); - }); } + + return action; }; -export const redoAction = (stages = []) => (dispatch, getState) => { +const getRedoAction = () => (dispatch, getState) => { const state = getState(); - let action = null; - - dispatch(setIsUndoRedoAction(true)); - const actions = state.undoableTrackingReducers.present; + + let action = { text: '' }; if (actions) { let actionsLenght = actions.undo_redo_actions_list.length; actionsLenght = actionsLenght > 0 ? actionsLenght - 1 : actionsLenght; action = actions.undo_redo_actions_list[actionsLenght]; + } + + return action; +}; + +const getNextUndoAction = () => (dispatch, getState) => { + const state = getState(); + const actionUndoList = state.undoableTrackingReducers.present; + + let action = { text: '' }; + let actions = actionUndoList && actionUndoList.undo_redo_actions_list; + if (actions) { + let actionsLenght = actions.length; + actionsLenght = actionsLenght > 0 ? actionsLenght - 1 : actionsLenght; + action = actions[actionsLenght]; + } + + return action; +}; + +const getNextRedoAction = () => (dispatch, getState) => { + const state = getState(); + const actionUndoList = state.undoableTrackingReducers.future; + + let action = { text: '' }; + let actionss = actionUndoList && actionUndoList[0]; + + let actions = actionss && actionss.undo_redo_actions_list; + if (actions) { + let actionsLenght = actions.length; + actionsLenght = actionsLenght > 0 ? actionsLenght - 1 : actionsLenght; + action = actions[actionsLenght]; + } + return action; +}; + +export const redoAction = (stages = []) => (dispatch, getState) => { + dispatch(setIsUndoRedoAction(true)); + let action = dispatch(getRedoAction()); + if (action) { Promise.resolve(dispatch(dispatch(handleRedoAction(action, stages)))).then(() => { dispatch(setIsUndoRedoAction(false)); }); @@ -1082,10 +1275,22 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { dispatch(handleMoleculeAction(action, 'vector', true, majorViewStage, state)); break; case actionType.VECTOR_SELECTED: - dispatch(setCurrentVector(undefined)); + dispatch(handleVectorAction(action, false)); break; case actionType.VECTOR_DESELECTED: - dispatch(setCurrentVector(action.object_name)); + dispatch(handleVectorAction(action, true)); + break; + case actionType.VECTOR_COUMPOUND_ADDED: + dispatch(handleVectorCompoundAction(action, false, majorViewStage)); + break; + case actionType.VECTOR_COUMPOUND_REMOVED: + dispatch(handleVectorCompoundAction(action, true, majorViewStage)); + break; + case actionType.CLASS_SELECTED: + dispatch(handleClassSelectedAction(action, false)); + break; + case actionType.CLASS_UPDATED: + dispatch(handleClassUpdatedAction(action, false)); break; case actionType.TARGET_LOADED: dispatch(handleTargetAction(action, false)); @@ -1102,14 +1307,26 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART: dispatch(handleShoppingCartAction(action, true)); break; + case actionType.MOLECULE_ADDED_TO_SHOPPING_CART_ALL: + dispatch(handleShoppingCartAllAction(action, false, majorViewStage)); + break; + case actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART_ALL: + dispatch(handleShoppingCartAllAction(action, true, majorViewStage)); + break; case actionType.COMPOUND_SELECTED: dispatch(handleCompoundAction(action, false)); break; case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, true)); break; - case actionType.REPRESENTATION_CHANGED: - dispatch(handleChangeRepresentationAction(action, false, majorView)); + case actionType.REPRESENTATION_VISIBILITY_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAction(action, false, majorView)); + break; + case actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAllAction(action, false, majorView)); + break; + case actionType.REPRESENTATION_UPDATED: + dispatch(handleUpdateRepresentationAction(action, false, majorView)); break; case actionType.REPRESENTATION_ADDED: dispatch(handleRepresentationAction(action, false, majorView)); @@ -1117,6 +1334,27 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.REPRESENTATION_REMOVED: dispatch(handleRepresentationAction(action, true, majorView)); break; + case actionType.REPRESENTATION_CHANGED: + dispatch(handleChangeRepresentationAction(action, false, majorView)); + break; + case actionType.BACKGROUND_COLOR_CHANGED: + dispatch(setNglBckGrndColor(action.oldSetting, majorViewStage, stageSummaryView)); + break; + case actionType.CLIP_NEAR: + dispatch(setNglClipNear(action.oldSetting, action.newSetting, majorViewStage)); + break; + case actionType.CLIP_FAR: + dispatch(setNglClipFar(action.oldSetting, action.newSetting, majorViewStage)); + break; + case actionType.CLIP_DIST: + dispatch(setNglClipDist(action.oldSetting, action.newSetting, majorViewStage)); + break; + case actionType.FOG_NEAR: + dispatch(setNglFogNear(action.oldSetting, action.newSetting, majorViewStage)); + break; + case actionType.FOG_FAR: + dispatch(setNglFogFar(action.oldSetting, action.newSetting, majorViewStage)); + break; default: break; } @@ -1180,10 +1418,22 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { dispatch(handleMoleculeAction(action, 'vector', false, majorViewStage, state)); break; case actionType.VECTOR_SELECTED: - dispatch(setCurrentVector(action.object_name)); + dispatch(handleVectorAction(action, true)); break; case actionType.VECTOR_DESELECTED: - dispatch(setCurrentVector(undefined)); + dispatch(handleVectorAction(action, false)); + break; + case actionType.VECTOR_COUMPOUND_ADDED: + dispatch(handleVectorCompoundAction(action, true)); + break; + case actionType.VECTOR_COUMPOUND_REMOVED: + dispatch(handleVectorCompoundAction(action, false)); + break; + case actionType.CLASS_SELECTED: + dispatch(handleClassSelectedAction(action, true)); + break; + case actionType.CLASS_UPDATED: + dispatch(handleClassUpdatedAction(action, true)); break; case actionType.TARGET_LOADED: dispatch(handleTargetAction(action, true)); @@ -1200,14 +1450,26 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART: dispatch(handleShoppingCartAction(action, false)); break; + case actionType.MOLECULE_ADDED_TO_SHOPPING_CART_ALL: + dispatch(handleShoppingCartAllAction(action, true, majorViewStage)); + break; + case actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART_ALL: + dispatch(handleShoppingCartAllAction(action, false, majorViewStage)); + break; case actionType.COMPOUND_SELECTED: dispatch(handleCompoundAction(action, true)); break; case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, false)); break; - case actionType.REPRESENTATION_CHANGED: - dispatch(handleChangeRepresentationAction(action, true, majorView)); + case actionType.REPRESENTATION_VISIBILITY_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAction(action, true, majorView)); + break; + case actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAllAction(action, true, majorView)); + break; + case actionType.REPRESENTATION_UPDATED: + dispatch(handleUpdateRepresentationAction(action, true, majorView)); break; case actionType.REPRESENTATION_ADDED: dispatch(handleRepresentationAction(action, true, majorView)); @@ -1215,6 +1477,27 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.REPRESENTATION_REMOVED: dispatch(handleRepresentationAction(action, false, majorView)); break; + case actionType.REPRESENTATION_CHANGED: + dispatch(handleChangeRepresentationAction(action, true, majorView)); + break; + case actionType.BACKGROUND_COLOR_CHANGED: + dispatch(setNglBckGrndColor(action.newSetting, majorViewStage, stageSummaryView)); + break; + case actionType.CLIP_NEAR: + dispatch(setNglClipNear(action.newSetting, action.oldSetting, majorViewStage)); + break; + case actionType.CLIP_FAR: + dispatch(setNglClipFar(action.newSetting, action.oldSetting, majorViewStage)); + break; + case actionType.CLIP_DIST: + dispatch(setNglClipDist(action.newSetting, action.oldSetting, majorViewStage)); + break; + case actionType.FOG_NEAR: + dispatch(setNglFogNear(action.newSetting, action.oldSetting, majorViewStage)); + break; + case actionType.FOG_FAR: + dispatch(setNglFogFar(action.newSetting, action.oldSetting, majorViewStage)); + break; default: break; } @@ -1408,6 +1691,43 @@ const handleAllAction = (action, isSelected, majorViewStage, state) => (dispatch } }; +const handleVectorAction = (action, isSelected) => (dispatch, getState) => { + if (action) { + if (isSelected === false) { + dispatch(selectVectorAndResetCompounds(undefined)); + } else { + dispatch(selectVectorAndResetCompounds(action.object_name)); + } + } +}; + +const handleVectorCompoundAction = (action, isSelected, majorViewStage) => (dispatch, getState) => { + if (action) { + let data = action.item; + let compoundId = action.compoundId; + dispatch(handleShowVectorCompound({ isSelected, data, index: compoundId, majorViewStage: majorViewStage })); + } +}; + +const handleClassSelectedAction = (action, isAdd) => (dispatch, getState) => { + if (action) { + let value = isAdd ? action.value : action.oldValue; + let oldValue = isAdd ? action.oldValue : action.value; + dispatch(setCurrentCompoundClass(value, oldValue)); + } +}; + +const handleClassUpdatedAction = (action, isAdd) => (dispatch, getState) => { + if (action) { + let id = action.object_id; + let newValue = isAdd ? action.newCompoundClasses : action.oldCompoundClasses; + let oldValue = isAdd ? action.oldCompoundClasses : action.newCompoundClasses; + let value = isAdd ? action.object_name : action.oldCompoundClasses[id]; + value = value !== undefined ? value : ''; + dispatch(setCompoundClasses(newValue, oldValue, value, id)); + } +}; + const handleTargetAction = (action, isSelected, stages) => (dispatch, getState) => { const state = getState(); if (action) { @@ -1440,25 +1760,34 @@ const handleCompoundAction = (action, isSelected) => (dispatch, getState) => { const handleShoppingCartAction = (action, isAdd) => (dispatch, getState) => { if (action) { let data = action.item; - if (isAdd) { - dispatch(appendToBuyList(data)); - } else { - dispatch(removeFromToBuyList(data)); + let compoundId = action.compoundId; + + if (data) { + dispatch(handleBuyList({ isSelected: isAdd, data, compoundId })); } } }; +const handleShoppingCartAllAction = (action, isAdd, majorViewStage) => (dispatch, getState) => { + if (action) { + dispatch(handleBuyListAll({ isSelected: isAdd, items: action.items, majorViewStage: majorViewStage })); + } +}; + const handleRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { if (action) { if (isAdd === true) { - dispatch(addRepresentation(action.object_id, action.representation, nglView)); + dispatch(addRepresentation(action, action.object_id, action.representation, nglView)); } else { - dispatch(removeRepresentation(action.object_id, action.representation, nglView)); + dispatch(removeRepresentation(action, action.object_id, action.representation, nglView)); } } }; -const addRepresentation = (parentKey, representation, nglView) => (dispatch, getState) => { +const addRepresentation = (action, parentKey, representation, nglView, update, skipTracking = false) => ( + dispatch, + getState +) => { const oldRepresentation = representation; const newRepresentationType = oldRepresentation.type; const comp = nglView.stage.getComponentsByName(parentKey).first; @@ -1468,16 +1797,101 @@ const addRepresentation = (parentKey, representation, nglView) => (dispatch, get comp, oldRepresentation.lastKnownID ); - dispatch(addComponentRepresentation(parentKey, newRepresentation)); + action.representation = newRepresentation; + if (update === true) { + action.newRepresentation = newRepresentation; + } else { + action.oldRepresentation = newRepresentation; + } + dispatch(addComponentRepresentation(parentKey, newRepresentation, skipTracking)); }; -const handleChangeRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { +const removeRepresentation = (action, parentKey, representation, nglView, skipTracking = false) => ( + dispatch, + getState +) => { + const comp = nglView.stage.getComponentsByName(parentKey).first; + let foundedRepresentation = undefined; + comp.eachRepresentation(r => { + if ( + r.uuid === representation.uuid || + r.uuid === representation.lastKnownID || + r.repr.type === representation.type + ) { + foundedRepresentation = r; + } + }); + + if (foundedRepresentation) { + comp.removeRepresentation(foundedRepresentation); + + if (comp.reprList.length === 0) { + dispatch(deleteObject(nglView, nglView.stage, true)); + } else { + dispatch(removeComponentRepresentation(parentKey, foundedRepresentation, skipTracking)); + } + } else { + console.log(`Not found representation:`, representation); + } +}; + +const handleUpdateRepresentationVisibilityAction = (action, isAdd, nglView) => (dispatch, getState) => { if (action) { - dispatch(changeRepresentation(isAdd, action.change, action.object_id, action.representation, nglView)); + let parentKey = action.object_id; + let representation = action.representation; + + const comp = nglView.stage.getComponentsByName(parentKey).first; + comp.eachRepresentation(r => { + if (r.uuid === representation.uuid || r.uuid === representation.lastKnownID) { + const newVisibility = isAdd ? action.value : !action.value; + // update in redux + representation.params.visible = newVisibility; + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); + dispatch( + updateComponentRepresentationVisibility(parentKey, representation.uuid, representation, newVisibility) + ); + // update in nglView + r.setVisibility(newVisibility); + } + }); } }; -const changeRepresentation = (isAdd, change, parentKey, representation, nglView) => (dispatch, getState) => { +const handleUpdateRepresentationVisibilityAllAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + const state = getState(); + let parentKey = action.object_id; + let objectsInView = state.nglReducers.objectsInView; + let newVisibility = isAdd ? action.value : !action.value; + + const representations = (objectsInView[parentKey] && objectsInView[parentKey].representations) || []; + const comp = nglView.stage.getComponentsByName(parentKey).first; + + if (representations) { + representations.forEach((representation, index) => { + comp.eachRepresentation(r => { + if (r.uuid === representation.uuid || r.uuid === representation.lastKnownID) { + representation.params.visible = newVisibility; + // update in nglView + r.setVisibility(newVisibility); + // update in redux + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); + } + }); + }); + + dispatch(updateComponentRepresentationVisibilityAll(parentKey, newVisibility)); + } + } +}; + +const handleUpdateRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + dispatch(updateRepresentation(isAdd, action.change, action.object_id, action.representation, nglView)); + } +}; + +const updateRepresentation = (isAdd, change, parentKey, representation, nglView) => (dispatch, getState) => { const comp = nglView.stage.getComponentsByName(parentKey).first; const r = comp.reprList.find(rep => rep.uuid === representation.uuid || rep.uuid === representation.lastKnownID); if (r && change) { @@ -1491,32 +1905,63 @@ const changeRepresentation = (isAdd, change, parentKey, representation, nglView) } }; -const removeRepresentation = (parentKey, representation, nglView) => (dispatch, getState) => { +const handleChangeRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + let representation = action.newRepresentation; + let type = action.oldRepresentation.type; + dispatch(changeMolecularRepresentation(action, representation, type, action.object_id, nglView)); + } +}; + +const changeMolecularRepresentation = (action, representation, type, parentKey, nglView) => (dispatch, getState) => { + const newRepresentationType = type; + + //const newRepresentationType = e.target.value; + const oldRepresentation = JSON.parse(JSON.stringify(representation)); + //const nglView = getNglView(objectsInView[parentKey].display_div); const comp = nglView.stage.getComponentsByName(parentKey).first; - let foundedRepresentation = undefined; - comp.eachRepresentation(r => { - if (r.uuid === representation.uuid || r.uuid === representation.lastKnownID) { - foundedRepresentation = r; - } - }); - if (foundedRepresentation) { - comp.removeRepresentation(foundedRepresentation); - if (comp.reprList.length === 0) { - dispatch(deleteObject(nglView, nglView.stage, true)); - } else { - dispatch(removeComponentRepresentation(parentKey, representation)); - } - } + // add representation to NGL + const newRepresentation = assignRepresentationToComp( + newRepresentationType, + oldRepresentation.params, + comp, + oldRepresentation.lastKnownID + ); + + action.newRepresentation = newRepresentation; + action.oldRepresentation = representation; + + // add new representation to redux + dispatch(addComponentRepresentation(parentKey, newRepresentation, true)); + + // remove previous representation from NGL + dispatch(removeRepresentation(action, parentKey, representation, nglView, true)); + + dispatch(changeComponentRepresentation(parentKey, oldRepresentation, newRepresentation)); }; const handleMoleculeGroupAction = (action, isSelected, stageSummaryView, majorViewStage) => (dispatch, getState) => { const state = getState(); if (action) { - let moleculeGroup = getMolGroup(action.object_name, state); + const { selectionGroups, object_name } = action; + let moleculeGroup = getMolGroup(object_name, state); if (moleculeGroup) { if (isSelected === true) { dispatch(selectMoleculeGroup(moleculeGroup, stageSummaryView)); + + for (const type in selectionGroups) { + if (selectionGroups.hasOwnProperty(type)) { + const typeGroup = selectionGroups[type]; + for (const mol of typeGroup) { + if (type === 'ligand') { + dispatch(addType[type](majorViewStage, mol, colourList[mol.id % colourList.length], true, true)); + } else { + dispatch(addType[type](majorViewStage, mol, colourList[mol.id % colourList.length], true)); + } + } + } + } } else { dispatch(onDeselectMoleculeGroup({ moleculeGroup, stageSummaryView, majorViewStage })); } @@ -1592,22 +2037,73 @@ export const getCanRedo = () => (dispatch, getState) => { return state.undoableTrackingReducers.future.length > 0; }; +export const getUndoActionText = () => (dispatch, getState) => { + let action = dispatch(getNextUndoAction()); + return action?.text ?? ''; +}; + +export const getRedoActionText = () => (dispatch, getState) => { + let action = dispatch(getNextRedoAction()); + return action?.text ?? ''; +}; + export const appendAndSendTrackingActions = trackAction => (dispatch, getState) => { const state = getState(); const isUndoRedoAction = state.trackingReducers.isUndoRedoAction; + dispatch(setIsActionTracking(true)); if (trackAction && trackAction !== null) { - dispatch(appendToActionList(trackAction, isUndoRedoAction)); - dispatch(appendToSendActionList(trackAction)); + const actionList = state.trackingReducers.track_actions_list; + const sendActionList = state.trackingReducers.send_actions_list; + const mergedActionList = mergeActions(trackAction, [...actionList]); + const mergedSendActionList = mergeActions(trackAction, [...sendActionList]); + dispatch(setActionsList(mergedActionList)); + dispatch(setSendActionsList(mergedSendActionList)); if (isUndoRedoAction === false) { - dispatch(appendToUndoRedoActionList(trackAction)); + const undoRedoActionList = state.trackingReducers.undo_redo_actions_list; + const mergedUndoRedoActionList = mergeActions(trackAction, [...undoRedoActionList]); + dispatch(setUndoRedoActionList(mergedUndoRedoActionList)); } } - + dispatch(setIsActionTracking(false)); dispatch(checkSendTrackingActions()); }; +export const mergeActions = (trackAction, list) => { + if (needsToBeMerged(trackAction)) { + let newList = []; + if (list.length > 0) { + const lastEntry = list[list.length - 1]; + if (isSameTypeOfAction(trackAction, lastEntry) && isActionWithinTimeLimit(lastEntry, trackAction)) { + trackAction.oldSetting = lastEntry.oldSetting; + trackAction.text = trackAction.getText(); + newList = [...list.slice(0, list.length - 1), trackAction]; + } else { + newList = [...list, trackAction]; + } + } else { + newList.push(trackAction); + } + return newList; + } else { + return [...list, trackAction]; + } +}; + +const needsToBeMerged = trackAction => { + return trackAction.merge !== undefined ? trackAction.merge : false; +}; + +const isSameTypeOfAction = (firstAction, secondAction) => { + return firstAction.type === secondAction.type; +}; + +const isActionWithinTimeLimit = (firstAction, secondAction) => { + const diffInSeconds = Math.abs(firstAction.timestamp - secondAction.timestamp) / 1000; + return diffInSeconds <= NUM_OF_SECONDS_TO_IGNORE_MERGE; +}; + export const manageSendTrackingActions = (projectID, copy) => (dispatch, getState) => { if (copy) { dispatch(checkActionsProject(projectID)); @@ -1627,7 +2123,7 @@ export const checkSendTrackingActions = (save = false) => (dispatch, getState) = } }; -const sendTrackingActions = (sendActions, project, clear = true) => (dispatch, getState) => { +const sendTrackingActions = (sendActions, project, clear = true) => async (dispatch, getState) => { if (project) { const projectID = project && project.projectID; @@ -1668,11 +2164,11 @@ export const setProjectTrackingActions = () => (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const projectID = currentProject && currentProject.projectID; - - dispatch(getTrackingActions(projectID)); + dispatch(setProjectActionList([])); + dispatch(getTrackingActions(projectID, true)); }; -const getTrackingActions = projectID => (dispatch, getState) => { +const getTrackingActions = (projectID, withTreeSeparation) => (dispatch, getState) => { const state = getState(); const sendActions = state.trackingReducers.send_actions_list; @@ -1686,9 +2182,23 @@ const getTrackingActions = projectID => (dispatch, getState) => { let listToSet = []; results.forEach(r => { let resultActions = JSON.parse(r.actions); - listToSet.push(...resultActions); + let actions = resultActions.map(obj => ({ ...obj, actionId: r.id })); + listToSet.push(...actions); }); + if (withTreeSeparation === true) { + listToSet = dispatch(separateTrackkingActionBySnapshotTree(listToSet)); + + let actionsWithoutSnapshot = listToSet.filter(action => action.type !== actionType.SNAPSHOT); + let snapshotActions = listToSet.filter(action => action.type === actionType.SNAPSHOT); + if (snapshotActions) { + const key = 'object_id'; + const arrayUniqueByKey = [...new Map(snapshotActions.map(item => [item[key], item])).values()]; + actionsWithoutSnapshot.push(...arrayUniqueByKey); + listToSet = actionsWithoutSnapshot; + } + } + let projectActions = [...listToSet, ...sendActions]; dispatch(setProjectActionList(projectActions)); return Promise.resolve(projectActions); @@ -1706,17 +2216,52 @@ const getTrackingActions = projectID => (dispatch, getState) => { } }; -const checkActionsProject = projectID => (dispatch, getState) => { +const separateTrackkingActionBySnapshotTree = actionList => (dispatch, getState) => { + const state = getState(); + const snapshotID = state.projectReducers.currentSnapshot && state.projectReducers.currentSnapshot.id; + const currentSnapshotTree = state.projectReducers.currentSnapshotTree; + const currentSnapshotList = state.projectReducers.currentSnapshotList; + + if (snapshotID && currentSnapshotTree != null) { + let treeActionList = []; + let snapshotIdList = []; + snapshotIdList.push(currentSnapshotTree.id); + + if (currentSnapshotList != null) { + for (const id in currentSnapshotList) { + let snapshot = currentSnapshotList[id]; + let snapshotChildren = snapshot.children; + + if ( + (snapshotChildren && snapshotChildren !== null && snapshotChildren.includes(snapshotID)) || + snapshot.id === snapshotID + ) { + snapshotIdList.push(snapshot.id); + } + } + } + + treeActionList = actionList.filter( + a => snapshotIdList.includes(a.snapshotId) || a.snapshotId === null || a.snapshotId === undefined + ); + return treeActionList; + } else { + return actionList; + } +}; + +const checkActionsProject = projectID => async (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const currentProjectID = currentProject && currentProject.projectID; - Promise.resolve(dispatch(getTrackingActions(projectID))).then(() => { - dispatch(copyActionsToProject(currentProject, true, currentProjectID && currentProjectID != null ? true : false)); - }); + await dispatch(getTrackingActions(projectID)); + await dispatch( + copyActionsToProject(currentProject, true, currentProjectID && currentProjectID != null ? true : false) + ); }; -const copyActionsToProject = (toProject, setActionList = true, clearSendList = true) => (dispatch, getState) => { +const copyActionsToProject = (toProject, setActionList = true, clearSendList = true) => async (dispatch, getState) => { const state = getState(); const actionList = state.trackingReducers.project_actions_list; @@ -1730,20 +2275,19 @@ const copyActionsToProject = (toProject, setActionList = true, clearSendList = t if (setActionList === true) { dispatch(setActionsList(newActionsList)); } - dispatch(sendTrackingActions(newActionsList, toProject, clearSendList)); + await dispatch(sendTrackingActions(newActionsList, toProject, clearSendList)); } }; -export const sendTrackingActionsByProjectId = (projectID, authorID) => (dispatch, getState) => { +export const sendTrackingActionsByProjectId = (projectID, authorID) => async (dispatch, getState) => { const state = getState(); const currentProject = state.projectReducers.currentProject; const currentProjectID = currentProject && currentProject.projectID; const project = { projectID, authorID }; - Promise.resolve(dispatch(getTrackingActions(currentProjectID))).then(() => { - dispatch(copyActionsToProject(project, false, currentProjectID && currentProjectID != null ? true : false)); - }); + await dispatch(getTrackingActions(currentProjectID)); + await dispatch(copyActionsToProject(project, false, currentProjectID && currentProjectID != null ? true : false)); }; export const sendInitTrackingActionByProjectId = target_on => (dispatch, getState) => { @@ -1759,3 +2303,79 @@ export const sendInitTrackingActionByProjectId = target_on => (dispatch, getStat dispatch(saveTrackingActions(actions, snapshotID)); } }; + +export const updateTrackingActions = action => (dispatch, getState) => { + const state = getState(); + const project = state.projectReducers.currentProject; + const projectActions = state.trackingReducers.project_actions_list; + const projectID = project && project.projectID; + let actionID = action && action.actionId; + + if (projectID && actionID && projectActions) { + let actions = projectActions.filter(a => a.actionId === actionID); + + if (actions && actions.length > 0) { + const dataToSend = { + session_action_id: actionID, + session_project: projectID, + author: project.authorID, + last_update_date: moment().format(), + actions: JSON.stringify(actions) + }; + return api({ + url: `${base_url}/api/session-actions/${actionID}`, + method: METHOD.PUT, + data: JSON.stringify(dataToSend) + }) + .then(() => {}) + .catch(error => { + throw new Error(error); + }) + .finally(() => {}); + } else { + return Promise.resolve(); + } + } else { + return Promise.resolve(); + } +}; + +function groupArrayOfObjects(list, key) { + return list.reduce(function(rv, x) { + (rv[x[key]] = rv[x[key]] || []).push(x); + return rv; + }, {}); +} + +export const setAndUpdateTrackingActions = (actionList, projectID) => (dispatch, getState) => { + if (projectID) { + const groupBy = groupArrayOfObjects(actionList, 'actionId'); + + for (const group in groupBy) { + let actionID = group; + let actions = groupBy[group]; + if (actionID && actions && actions.length > 0) { + const dataToSend = { + session_action_id: actionID, + session_project: projectID, + last_update_date: moment().format(), + actions: JSON.stringify(actions) + }; + return api({ + url: `${base_url}/api/session-actions/${actionID}`, + method: METHOD.PUT, + data: JSON.stringify(dataToSend) + }) + .then(() => {}) + .catch(error => { + throw new Error(error); + }) + .finally(() => {}); + } else { + return Promise.resolve(); + } + } + } else { + return Promise.resolve(); + } +}; diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js index 963671514..7fcc7414f 100644 --- a/js/reducers/tracking/trackingActions.js +++ b/js/reducers/tracking/trackingActions.js @@ -1,14 +1,17 @@ -import { actionType, actionObjectType, actionDescription } from './constants'; +import { actionType, actionObjectType, actionDescription, actionAnnotation } from './constants'; import { constants as apiConstants } from '../api/constants'; import { CONSTANTS as nglConstants } from '../ngl/constants'; +import { constants as previewCompoundConstants } from '../../components/preview/compounds/redux/constants'; import { constants as selectionConstants } from '../selection/constants'; import { constants as customDatasetConstants } from '../../components/datasets/redux/constants'; import { DJANGO_CONTEXT } from '../../utils/djangoContext'; +import { NGL_PARAMS, BACKGROUND_COLOR } from '../../components/nglView/constants/index'; export const findTrackAction = (action, state) => { const username = DJANGO_CONTEXT['username']; const target_on_name = state.apiReducers.target_on_name; const isActionRestoring = state.trackingReducers.isActionRestoring; + const viewParams = state.nglReducers.viewParams; let trackAction = null; if (isActionRestoring === false && action.skipTracking !== true) { @@ -17,6 +20,7 @@ export const findTrackAction = (action, state) => { let targetName = getTargetName(action.target_on, state); trackAction = { type: actionType.TARGET_LOADED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.TARGET, @@ -33,6 +37,7 @@ export const findTrackAction = (action, state) => { let molGroupName = getMolGroupName(action.mol_group_on, state); trackAction = { type: actionType.SITE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.SITE, @@ -42,12 +47,26 @@ export const findTrackAction = (action, state) => { }; } } + } else if (action.type.includes(apiConstants.SET_MOL_GROUP_OFF)) { + const { mol_group_off, selectionGroups } = action; + let molGroupName = getMolGroupName(mol_group_off, state); + trackAction = { + type: actionType.SITE_TURNED_OFF, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.SITE, + object_name: molGroupName, + object_id: mol_group_off, + selectionGroups, + text: `${actionDescription.SITE} ${molGroupName} ${actionDescription.TURNED_OFF}` + }; } else if (action.type.includes(selectionConstants.SET_OBJECT_SELECTION)) { let objectId = action.payload && action.payload[0]; if (objectId) { let molGroupName = getMolGroupName(objectId, state); trackAction = { type: actionType.SITE_TURNED_OFF, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.SITE, @@ -63,6 +82,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_HIDE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -77,6 +97,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -99,6 +120,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -122,6 +144,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON_BY_TYPE, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -138,6 +161,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF_BY_TYPE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -153,6 +177,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -171,6 +196,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -189,6 +215,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -207,6 +234,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -225,6 +253,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -243,6 +272,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -261,6 +291,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -279,6 +310,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -297,6 +329,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTORS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -315,6 +348,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTORS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -326,38 +360,100 @@ export const findTrackAction = (action, state) => { )}` }; } - } else if (action.type.includes(selectionConstants.APPEND_TO_BUY_LIST)) { + } else if (action.type === selectionConstants.APPEND_TO_BUY_LIST) { if (action.item) { let objectType = actionObjectType.MOLECULE; - let objectName = action.vector; + let objectName = action.item && action.item.vector; trackAction = { type: actionType.MOLECULE_ADDED_TO_SHOPPING_CART, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, - object_type: actionObjectType.MOLECULE, + object_type: objectType, object_name: objectName, object_id: objectName, + compoundId: action.item.compoundId, item: action.item, - text: `${objectType} ${objectName} ${actionDescription.ADDED} ${actionDescription.TO_SHOPPING_CART}` + text: `${actionDescription.VECTOR} ${objectName} ${actionDescription.ADDED} ${actionDescription.TO_SHOPPING_CART}` }; } - } else if (action.type.includes(selectionConstants.REMOVE_FROM_TO_BUY_LIST)) { + } else if (action.type === selectionConstants.REMOVE_FROM_TO_BUY_LIST) { if (action.item) { let objectType = actionObjectType.MOLECULE; - let objectName = action.vector; + let objectName = action.item && action.item.vector; trackAction = { type: actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, object_name: objectName, object_id: objectName, + compoundId: action.item.compoundId, item: action.item, - text: `${objectType} ${objectName} ${actionDescription.REMOVED} ${actionDescription.FROM_SHOPPING_CART}` + text: `${actionDescription.VECTOR} ${objectName} ${actionDescription.REMOVED} ${actionDescription.FROM_SHOPPING_CART}` }; } + } else if (action.type === selectionConstants.APPEND_TO_BUY_LIST_ALL) { + let items = action.items; + let objectType = actionObjectType.COMPOUND; + + trackAction = { + type: actionType.MOLECULE_ADDED_TO_SHOPPING_CART_ALL, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + items: items, + text: `${actionDescription.ALL} ${actionDescription.ADDED} ${actionDescription.TO_SHOPPING_CART}` + }; + } else if (action.type === selectionConstants.REMOVE_FROM_BUY_LIST_ALL) { + let items = action.items; + let objectType = actionObjectType.COMPOUND; + + trackAction = { + type: actionType.MOLECULE_REMOVED_FROM_SHOPPING_CART_ALL, + annotation: actionAnnotation.CLEAR, + timestamp: Date.now(), + username: username, + object_type: objectType, + items: items, + text: `${actionDescription.ALL} ${actionDescription.REMOVED} ${actionDescription.FROM_SHOPPING_CART}` + }; + } else if (action.type === previewCompoundConstants.APPEND_SHOWED_COMPOUND_LIST) { + let objectType = actionObjectType.COMPOUND; + let objectName = action.item && action.item.vector; + + trackAction = { + type: actionType.VECTOR_COUMPOUND_ADDED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: objectName, + object_id: action.payload, + item: action.item, + compoundId: action.payload, + text: `${actionDescription.COMPOUND} ${objectName} ${actionDescription.ADDED}` + }; + } else if (action.type === previewCompoundConstants.REMOVE_SHOWED_COMPOUND_LIST) { + let objectType = actionObjectType.COMPOUND; + let objectName = action.item && action.item.vector; + + trackAction = { + type: actionType.VECTOR_COUMPOUND_REMOVED, + annotation: actionAnnotation.CLEAR, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: objectName, + object_id: action.payload, + item: action.item, + compoundId: action.payload, + text: `${actionDescription.COMPOUND} ${objectName} ${actionDescription.REMOVED}` + }; } else if (action.type.includes(selectionConstants.SET_CURRENT_VECTOR)) { if (action.payload) { let objectType = actionObjectType.MOLECULE; @@ -365,6 +461,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.VECTOR_SELECTED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -373,6 +470,42 @@ export const findTrackAction = (action, state) => { text: `${actionDescription.VECTOR} ${objectName} ${actionDescription.SELECTED}` }; } + } else if (action.type === previewCompoundConstants.SET_CURRENT_COMPOUND_CLASS) { + if (action.payload) { + let objectType = actionObjectType.COMPOUND; + let objectName = action.payload; + let oldObjectName = action.oldCompoundClass; + + trackAction = { + type: actionType.CLASS_SELECTED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: objectName, + object_id: objectName, + oldValue: oldObjectName, + value: objectName, + text: `${actionDescription.CLASS} ${objectName} ${actionDescription.SELECTED}` + }; + } + } else if (action.type === previewCompoundConstants.SET_COMPOUND_CLASSES) { + if (action.payload) { + let objectType = actionObjectType.COMPOUND; + + trackAction = { + type: actionType.CLASS_UPDATED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: action.value, + object_id: action.id, + newCompoundClasses: action.payload, + oldCompoundClasses: action.oldCompoundClasses, + text: `${actionDescription.CLASS} value ${actionDescription.UPDATED}: ${action.id}:${action.value}` + }; + } } else if (action.type.includes(customDatasetConstants.APPEND_MOLECULE_TO_COMPOUNDS_TO_BUY_OF_DATASET)) { if (action.payload) { let objectType = actionObjectType.COMPOUND; @@ -380,6 +513,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.COMPOUND_SELECTED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -396,6 +530,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.COMPOUND_DESELECTED, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -413,6 +548,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -434,6 +570,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -457,6 +594,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_ON_BY_TYPE, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -475,6 +613,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.ALL_TURNED_OFF_BY_TYPE, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -491,6 +630,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -508,6 +648,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.LIGAND_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -525,6 +666,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -542,6 +684,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SIDECHAINS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -559,6 +702,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -576,6 +720,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.INTERACTIONS_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -593,6 +738,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_ON, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: objectType, @@ -610,6 +756,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.SURFACE_TURNED_OFF, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -619,11 +766,53 @@ export const findTrackAction = (action, state) => { text: `${actionDescription.SURFACE} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}` }; } + } else if (action.type === nglConstants.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY) { + let objectType = actionObjectType.REPRESENTATION; + let value = action.newVisibility; + let valueDescription = value === true ? actionDescription.VISIBLE : actionDescription.HIDDEN; + + trackAction = { + type: actionType.REPRESENTATION_VISIBILITY_UPDATED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.REPRESENTATION, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + representation_id: action.representationID, + representation: action.representation, + value: value, + text: `${objectType} '${action.representation?.type}' ${actionDescription.VISIBILITY} of ${action.objectInViewID} ${actionDescription.CHANGED} to: ${valueDescription}` + }; + } else if (action.type === nglConstants.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL) { + let objectType = actionObjectType.REPRESENTATION; + let value = action.newVisibility; + let valueDescription = value === true ? actionDescription.VISIBLE : actionDescription.HIDDEN; + + trackAction = { + type: actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.REPRESENTATION, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + value: value, + text: `${objectType} ${actionDescription.VISIBILITY} of ${action.objectInViewID} ${actionDescription.CHANGED} to: ${valueDescription}` + }; } else if (action.type.includes(nglConstants.UPDATE_COMPONENT_REPRESENTATION)) { let objectType = actionObjectType.REPRESENTATION; + let key = action.change?.key; + let oldValue = action.change?.oldValue; + let newValue = action.change?.value; + let valueDescription = + key !== 'clipCenter' + ? `from value: ${oldValue} to value: ${newValue}` + : getClipCenterChange(oldValue, newValue); trackAction = { - type: actionType.REPRESENTATION_CHANGED, + type: actionType.REPRESENTATION_UPDATED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.REPRESENTATION, @@ -632,7 +821,7 @@ export const findTrackAction = (action, state) => { representation_id: action.representationID, representation: action.newRepresentation, change: action.change, - text: `${objectType} '${action.change?.key}' of ${action.objectInViewID} ${actionDescription.CHANGED} from value: ${action.change?.oldValue} to value: ${action.change?.value}` + text: `${objectType} '${key}' of ${action.objectInViewID} ${actionDescription.UPDATED} ${valueDescription}` }; } else if (action.type.includes(nglConstants.ADD_COMPONENT_REPRESENTATION)) { let objectType = actionObjectType.REPRESENTATION; @@ -640,6 +829,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.REPRESENTATION_ADDED, + annotation: actionAnnotation.CHECK, timestamp: Date.now(), username: username, object_type: actionObjectType.REPRESENTATION, @@ -654,6 +844,7 @@ export const findTrackAction = (action, state) => { trackAction = { type: actionType.REPRESENTATION_REMOVED, + annotation: actionAnnotation.CLEAR, timestamp: Date.now(), username: username, object_type: objectType, @@ -662,6 +853,168 @@ export const findTrackAction = (action, state) => { representation: action.representation, text: `${objectType} '${representationName}' of ${action.objectInViewID} ${actionDescription.REMOVED}` }; + } else if (action.type.includes(nglConstants.CHANGE_COMPONENT_REPRESENTATION)) { + let objectType = actionObjectType.REPRESENTATION; + let oldRepresentationName = action.oldRepresentation && action.oldRepresentation.type; + let newRepresentationName = action.newRepresentation && action.newRepresentation.type; + + trackAction = { + type: actionType.REPRESENTATION_CHANGED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + oldRepresentation: action.oldRepresentation, + newRepresentation: action.newRepresentation, + text: `${objectType} of ${action.objectInViewID} ${actionDescription.CHANGED} from value: ${oldRepresentationName} to value: ${newRepresentationName}` + }; + } else if (action.type.includes(nglConstants.SET_BACKGROUND_COLOR)) { + let oldSetting = action.payload === BACKGROUND_COLOR.white ? BACKGROUND_COLOR.black : BACKGROUND_COLOR.white; + let newSetting = action.payload; + + trackAction = { + type: actionType.BACKGROUND_COLOR_CHANGED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + text: `Color of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; + } else if (action.type.includes(nglConstants.SET_CLIP_NEAR)) { + let oldSetting = action.payload.oldValue; + let newSetting = action.payload.newValue; + + trackAction = { + type: actionType.CLIP_NEAR, + merge: true, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + getText: function() { + return ( + 'Clip near of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); + }, + text: `Clip near of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; + } else if (action.type.includes(nglConstants.SET_CLIP_FAR)) { + let oldSetting = action.payload.oldValue; + let newSetting = action.payload.newValue; + + trackAction = { + type: actionType.CLIP_FAR, + merge: true, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + getText: function() { + return ( + 'Clip far of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); + }, + text: `Clip far of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; + } else if (action.type.includes(nglConstants.SET_CLIP_DIST)) { + let oldSetting = action.payload.oldValue; + let newSetting = action.payload.newValue; + + trackAction = { + type: actionType.CLIP_DIST, + merge: true, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + getText: function() { + return ( + 'Clip dist of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); + }, + text: `Clip dist of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; + } else if (action.type.includes(nglConstants.SET_FOG_NEAR)) { + let oldSetting = action.payload.oldValue; + let newSetting = action.payload.newValue; + + trackAction = { + type: actionType.FOG_NEAR, + merge: true, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + getText: function() { + return ( + 'Fog near of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); + }, + text: `For near of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; + } else if (action.type.includes(nglConstants.SET_FOG_FAR)) { + let oldSetting = action.payload.oldValue; + let newSetting = action.payload.newValue; + + trackAction = { + type: actionType.FOG_FAR, + merge: true, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: 'NGL', + object_name: 'NGL', + oldSetting: oldSetting, + newSetting: newSetting, + getText: function() { + return ( + 'Fog far of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); + }, + text: `For far of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` + }; } } return trackAction; @@ -717,6 +1070,22 @@ const getTypeDescriptionOfSelectedAllAction = type => { } }; +const getClipCenterChange = (oldValue, newValue) => { + let description = ''; + if (oldValue && newValue) { + if (oldValue.x !== newValue.x) { + description += ' from value: x:' + oldValue.x + ' to value: x:' + newValue.x; + } + if (oldValue.y !== newValue.y) { + description += ' from value: y:' + oldValue.y + ' to value: y:' + newValue.y; + } + if (oldValue.z !== newValue.z) { + description += ' from value: z:' + oldValue.z + ' to value: z:' + newValue.z; + } + } + return description; +}; + export const createInitAction = target_on => (dispatch, getState) => { const state = getState(); const username = DJANGO_CONTEXT['username']; diff --git a/js/reducers/tracking/trackingReducers.js b/js/reducers/tracking/trackingReducers.js index bf8f5c569..20c6bb58e 100644 --- a/js/reducers/tracking/trackingReducers.js +++ b/js/reducers/tracking/trackingReducers.js @@ -13,8 +13,11 @@ export const INITIAL_STATE = { isActionSaving: false, send_actions_list: [], project_actions_list: [], + snapshotActionImageList: [], isActionRestoring: false, - isActionRestored: false + isActionRestored: false, + isActionTracking: false, + trackingImageSource: '' }; export function trackingReducers(state = INITIAL_STATE, action = {}) { @@ -33,10 +36,15 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { return Object.assign({}, state, { undo_redo_actions_list: [...new Set([...state.undo_redo_actions_list, action.track_action])] }); + + case constants.SET_UNDO_REDO_ACTIONS_LIST: + return { + ...state, undo_redo_actions_list: action.undo_redo_actions_list + }; case constants.SET_CURRENT_ACTIONS_LIST: return Object.assign({}, state, { - current_actions_list: action.current_actions_list + current_actions_list: [...action.current_actions_list] }); case constants.SET_IS_TRACKING_MOLECULES_RESTORING: @@ -66,7 +74,7 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { case constants.SET_SEND_ACTIONS_LIST: return Object.assign({}, state, { - send_actions_list: action.send_actions_list + send_actions_list: [...action.send_actions_list] }); case constants.APPEND_SEND_ACTIONS_LIST: @@ -79,6 +87,11 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { project_actions_list: action.project_actions_list }); + case constants.SET_SNAPSOT_IMAGE_ACTIONS_LIST: + return Object.assign({}, state, { + snapshotActionImageList: action.snapshotActionImageList + }); + case constants.SET_IS_ACTIONS_SAVING: return Object.assign({}, state, { isActionSaving: action.isActionSaving @@ -89,6 +102,15 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { isActionRestoring: action.isActionRestoring, isActionRestored: action.isActionRestored }); + case constants.SET_IS_ACTION_TRACKING: + return Object.assign({}, state, { + isActionTracking: action.isActionTracking + }); + + case constants.SET_TRACKING_IMAGE_SOURCE: + return Object.assign({}, state, { + trackingImageSource: action.payload + }); case constants.RESET_TRACKING_STATE: return INITIAL_STATE; @@ -100,5 +122,5 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { export const undoableTrackingReducers = undoable(trackingReducers, { limit: false, - filter: includeAction(constants.APPEND_UNDO_REDO_ACTIONS_LIST) + filter: includeAction(constants.SET_UNDO_REDO_ACTIONS_LIST) }); diff --git a/package.json b/package.json index 57f3b1e5a..b0edfcbb9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fragalysis-frontend", - "version": "0.9.38", + "version": "0.9.48", "description": "Frontend for fragalysis", "main": "webpack.config.js", "scripts": { @@ -50,10 +50,10 @@ "formik": "^2.1.4", "formik-material-ui": "^2.0.0-beta.1", "formik-material-ui-pickers": "^0.0.8", + "html2canvas": "^1.0.0-rc.7", "js-base64": "^2.5.2", "jszip": "^3.2.2", "lodash": "^4.17.15", - "logrocket": "^1.0.3", "moment": "^2.24.0", "ngl": "2.0.0-dev.37", "react": "^16.11.0", @@ -61,6 +61,7 @@ "react-color": "^2.17.3", "react-dom": "^16.12.0", "react-event-timeline": "^1.6.3", + "react-grid-gallery": "^0.5.5", "react-hot-loader": "^4.12.18", "react-infinite-scroller": "^1.2.4", "react-redux": "^7.1.3",