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/helpers/useEnableUserInteracion.js b/js/components/helpers/useEnableUserInteracion.js index afead7509..40ea5a2cc 100644 --- a/js/components/helpers/useEnableUserInteracion.js +++ b/js/components/helpers/useEnableUserInteracion.js @@ -12,9 +12,11 @@ export const useDisableUserInteraction = () => { const countOfPendingNglObjects = useSelector(state => state.nglReducers.countOfPendingNglObjects); const isLoadingTree = useSelector(state => state.projectReducers.isLoadingTree); const isLoadingCurrentSnapshot = useSelector(state => state.projectReducers.isLoadingCurrentSnapshot); + const isRestoring = useSelector(state => state.trackingReducers.isActionRestoring); useEffect(() => { if ( + isRestoring === false && isLoadingTree === false && isLoadingCurrentSnapshot === false && countOfPendingVectorLoadRequests === 0 && @@ -39,6 +41,7 @@ export const useDisableUserInteraction = () => { disableInteraction, isLoadingCurrentSnapshot, isLoadingTree, + isRestoring, proteinsHasLoaded ]); 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/redux/dispatchActions.js b/js/components/nglView/redux/dispatchActions.js index eec05e82c..4d8b2d0f6 100644 --- a/js/components/nglView/redux/dispatchActions.js +++ b/js/components/nglView/redux/dispatchActions.js @@ -8,7 +8,7 @@ import { setDuckYankData, setMolGroupOn, setPanddaSiteOn } from '../../../reduce import * as listTypes from '../../../constants/listTypes'; import { selectVectorAndResetCompounds } from '../../../reducers/selection/dispatchActions'; -export const toggleMoleculeGroup = (molGroupId, summaryViewStage, majorViewStage) => (dispatch, getState) => { +export const toggleMoleculeGroup = (molGroupId, summaryViewStage) => (dispatch, getState) => { const state = getState(); const molGroupSelection = state.selectionReducers.mol_group_selection; const objIdx = molGroupSelection.indexOf(molGroupId); @@ -58,11 +58,7 @@ export const toggleMoleculeGroup = (molGroupId, summaryViewStage, majorViewStage throw new Error(error); }); dispatch( - clearAfterDeselectingMoleculeGroup({ - molGroupId, - currentMolGroup, - majorViewStage - }) + clearAfterDeselectingMoleculeGroup() ); } }; @@ -117,9 +113,9 @@ export const handleNglViewPick = (stage, pickingProxy, getNglView) => (dispatch, const type = name.split('_')[0]; const pk = parseInt(name.split('_')[1], 10); if (type === OBJECT_TYPE.MOLECULE_GROUP && getNglView(VIEWS.MAJOR_VIEW)) { - dispatch(toggleMoleculeGroup(pk, stage, getNglView(VIEWS.MAJOR_VIEW).stage)); + dispatch(toggleMoleculeGroup(pk, stage)); } else if (type === OBJECT_TYPE.MOLGROUPS_SELECT && getNglView(VIEWS.MAJOR_VIEW)) { - dispatch(toggleMoleculeGroup(pk, stage, getNglView(VIEWS.MAJOR_VIEW).stage)); + dispatch(toggleMoleculeGroup(pk, stage)); } else if (type === listTypes.PANDDA_SITE) { dispatch(setPanddaSiteOn(pk)); } 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/molecule/moleculeList.js b/js/components/preview/molecule/moleculeList.js index a72b1e46e..5931d6268 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; @@ -292,11 +295,51 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei // Used for MoleculeListSortFilterDialog when using textSearch const joinedMoleculeListsCopy = useMemo(() => [...joinedMoleculeLists], [joinedMoleculeLists]); - if (isActiveFilter) { - joinedMoleculeLists = filterMolecules(joinedMoleculeLists, filter); - } else { + 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] + ); + joinedMoleculeLists = useMemo( + () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, complexList), + [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, complexList] + ); + joinedMoleculeLists = useMemo( + () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, fragmentDisplayList), + [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, fragmentDisplayList] + ); + joinedMoleculeLists = useMemo( + () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, surfaceList), + [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, surfaceList] + ); + joinedMoleculeLists = useMemo( + () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, densityList), + [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, densityList] + ); + joinedMoleculeLists = useMemo( + () => addSelectedMoleculesFromUnselectedSites(joinedMoleculeLists, vectorOnList), + [addSelectedMoleculesFromUnselectedSites, joinedMoleculeLists, vectorOnList] + ); + + if (!isActiveFilter) { // default sort is by site joinedMoleculeLists.sort((a, b) => a.site - b.site || a.number - b.number); + } else { + joinedMoleculeLists = filterMolecules(joinedMoleculeLists, filter); } const loadNextMolecules = () => { @@ -409,6 +452,12 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei } }, [isActiveFilter, setFilterItemsHeight]); + useEffect(() => { + if (!joinedMoleculeListsCopy.length) { + dispatch(setSortDialogOpen(false)); + } + }, [dispatch, joinedMoleculeListsCopy.length]); + const handleFilterChange = filter => { const filterSet = Object.assign({}, filter); for (let attr of MOL_ATTRIBUTES) { @@ -512,6 +561,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])); @@ -621,7 +672,7 @@ export const MoleculeList = memo(({ height, setFilterItemsHeight, filterItemsHei }; const actions = [ - +