diff --git a/js/components/datasets/datasetMoleculeList.js b/js/components/datasets/datasetMoleculeList.js index 529aa3418..e3931ba7a 100644 --- a/js/components/datasets/datasetMoleculeList.js +++ b/js/components/datasets/datasetMoleculeList.js @@ -13,7 +13,8 @@ import { ButtonGroup, TextField, Checkbox, - InputAdornment + InputAdornment, + setRef } from '@material-ui/core'; import React, { useState, useEffect, memo, useRef, useContext, useCallback, useMemo } from 'react'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; @@ -210,13 +211,24 @@ const useStyles = makeStyles(theme => ({ color: theme.palette.primary.main, fontStyle: 'italic' }, + arrowsHighlight: { + borderColor: theme.palette.primary.main, + border: 'solid 2px', + backgroundColor: theme.palette.primary.main + }, arrow: { - width: 12, - height: 15 + width: 20, + height: 25, + color: 'white', + stroke: 'white', + strokeWidth: 2 + }, + iconButton: { + padding: 0 }, invisArrow: { - width: 12, - height: 15, + width: 20, + height: 25, visibility: 'hidden' }, arrows: { @@ -241,11 +253,19 @@ const useStyles = makeStyles(theme => ({ backgroundColor: compoundsColors.apricot.color }, textField: { - // marginLeft: theme.spacing(1), + marginLeft: theme.spacing(0.5), // marginRight: theme.spacing(1), - width: 70, + width: 90, + borderRadius: 10, + '& .MuiFormLabel-root': { paddingLeft: theme.spacing(1) + }, + '& .MuiInput-underline:before': { + borderBottom: '0px solid' + }, + '& .MuiInput-underline:after': { + borderBottom: '0px solid' } }, selectedInput: { @@ -260,7 +280,8 @@ const useStyles = makeStyles(theme => ({ }, editClassNameIconSelected: { padding: '0px', - color: theme.palette.primary.main + // color: theme.palette.primary.main + color: 'red' } })); @@ -328,7 +349,7 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { const compoundsToBuyList = useSelector(state => state.datasetsReducers.compoundsToBuyDatasetMap[datasetID]); - const { addMoleculeViewRef, setScrollToMoleculeId } = useScrollToSelected( + const { addMoleculeViewRef, setScrollToMoleculeId, getNode } = useScrollToSelected( datasetID, moleculesPerPage, setCurrentPage @@ -365,6 +386,14 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { [compoundsColors.apricot.key]: apricotInput }; + const inputRefs = { + [compoundsColors.blue.key]: useRef(), + [compoundsColors.red.key]: useRef(), + [compoundsColors.green.key]: useRef(), + [compoundsColors.purple.key]: useRef(), + [compoundsColors.apricot.key]: useRef() + }; + const compoundColors = useSelector(state => state.datasetsReducers.compoundColorByDataset[datasetID]) ?? {}; const isSelectedTypeOn = typeList => { @@ -683,6 +712,7 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { if (firstItem && nextItem) { const moleculeTitleNext = nextItem && nextItem.name; + const node = getNode(nextItem.id); setScrollToMoleculeId(nextItem.id); let dataValue = { @@ -694,6 +724,10 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { }; dispatch(setCrossReferenceCompoundName(moleculeTitleNext)); + + if (node) { + setSelectedMoleculeRef(node); + } dispatch( moveDatasetMoleculeUpDown(stage, datasetID, firstItem, datasetID, nextItem, dataValue, ARROW_TYPE.DOWN) ); @@ -713,7 +747,7 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { if (firstItem && prevItem) { const moleculeTitlePrev = prevItem && prevItem.name; - + const node = getNode(prevItem.id); setScrollToMoleculeId(prevItem.id); let dataValue = { @@ -725,6 +759,9 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { }; dispatch(setCrossReferenceCompoundName(moleculeTitlePrev)); + if (node) { + setSelectedMoleculeRef(node); + } dispatch(moveDatasetMoleculeUpDown(stage, datasetID, firstItem, datasetID, prevItem, dataValue, ARROW_TYPE.UP)); } } @@ -820,34 +857,41 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { {Object.keys(compoundsColors).map(item => ( <> - - dispatch(onChangeCompoundClassCheckbox(e))} - checked={currentCompoundClass === item} - > - + endAdornment: ( + dispatch(onStartEditColorClassName(e))} + onClick={e => { + dispatch(onStartEditColorClassName(e)); + inputRefs[item].current.focus(); + inputRefs[item].current.select(); + }} > + ), + startAdornment: ( + + dispatch(onChangeCompoundClassCheckbox(e))} + checked={currentCompoundClass === item} + > + ) }} + inputRef={inputRefs[item]} autoComplete="off" id={`${item}`} key={`CLASS_${item}`} @@ -857,7 +901,6 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { classes[item], currentCompoundClass === item && classes.selectedInput )} - label={compoundsColors[item].text} onChange={e => dispatch(onChangeCompoundClassValue(e))} onKeyDown={e => dispatch(onKeyDownCompoundClass(e))} // onClick={e => dispatch(onClickCompoundClass(e))} @@ -958,23 +1001,35 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { - + - + @@ -1062,6 +1117,7 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { disableP={locked && groupDatasetsNglControlButtonsDisabledState.protein} disableC={locked && groupDatasetsNglControlButtonsDisabledState.complex} dragDropEnabled + getNode={getNode} /> ); })} diff --git a/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js b/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js index 94eb0bbeb..9fd292851 100644 --- a/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js +++ b/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js @@ -5,7 +5,13 @@ import React, { memo, useEffect, useState, useRef, useContext, forwardRef } from 'react'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; import { Grid, Button, makeStyles, Tooltip, IconButton } from '@material-ui/core'; -import { ClearOutlined, CheckOutlined, Assignment, AssignmentTurnedIn } from '@material-ui/icons'; +import { + ClearOutlined, + CheckOutlined, + Assignment, + AssignmentTurnedIn, + KeyboardReturnOutlined +} from '@material-ui/icons'; import SVGInline from 'react-svg-inline'; import classNames from 'classnames'; import { VIEWS, ARROW_TYPE } from '../../../constants/constants'; @@ -28,7 +34,12 @@ import { getFirstUnlockedCompoundBefore, isDatasetCompoundIterrable, isDatasetCompoundLocked, - getAllVisibleButNotLockedCompounds + getAllVisibleButNotLockedCompounds, + getAllVisibleButNotLockedSelectedCompounds, + isCompoundLocked, + getFirstUnlockedSelectedCompoundAfter, + moveSelectedDatasetMoleculeUpDown, + getFirstUnlockedSelectedCompoundBefore } from '../redux/dispatchActions'; import { isAnyInspirationTurnedOn, getFilteredDatasetMoleculeList } from '../redux/selectors'; @@ -357,7 +368,8 @@ const DatasetMoleculeView = memo( disableP, disableC, inSelectedCompoundsList = false, - colorButtonsEnabled = true + colorButtonsEnabled = true, + getNode }, outsideRef ) => { @@ -382,6 +394,8 @@ const DatasetMoleculeView = memo( const currentCompoundClass = useSelector(state => state.previewReducers.compounds.currentCompoundClass); + const selectedCompounds = useSelector(state => state.datasetsReducers.selectedCompounds); + const disableMoleculeNglControlButtons = useSelector(state => state.datasetsReducers.disableDatasetsNglControlButtons[datasetID]?.[currentID]) || {}; @@ -398,6 +412,9 @@ const DatasetMoleculeView = memo( const isSurfaceOn = S; const askLockCompoundsQuestion = useSelector(state => state.datasetsReducers.askLockCompoundsQuestion); + const askLockSelectedCompoundsQuestion = useSelector( + state => state.datasetsReducers.askLockSelectedCompoundsQuestion + ); const isLockVisibleCompoundsDialogOpenLocal = useSelector( state => state.datasetsReducers.isLockVisibleCompoundsDialogOpenLocal @@ -414,7 +431,8 @@ const DatasetMoleculeView = memo( const hasAllValuesOn = isLigandOn && isProteinOn && isComplexOn; const hasSomeValuesOn = !hasAllValuesOn && (isLigandOn || isProteinOn || isComplexOn || isSurfaceOn); - let areArrowsVisible = (isLigandOn || isProteinOn || isComplexOn || isSurfaceOn) && !isLocked; + let areArrowsVisible = + (isLigandOn || isProteinOn || isComplexOn || isSurfaceOn) && !isLocked && !isCompoundFromVectorSelector(data); if (arrowsHidden) { areArrowsVisible = false; @@ -643,20 +661,29 @@ const DatasetMoleculeView = memo( } }; - const handleClickOnDownArrow = async event => { - const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedCompounds(datasetID, currentID)); - if (unlockedVisibleCompounds?.length > 0 && askLockCompoundsQuestion) { + const moveDownSelectedCompound = anchorEl => { + //check if there are unlocked but visible compounds in the selected compounds list + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedSelectedCompounds(datasetID, currentID)); + if (unlockedVisibleCompounds?.length > 0 && askLockSelectedCompoundsQuestion) { dispatch(setCmpForLocalLockVisibleCompoundsDialog(data)); - setLockCompoundsDialogAnchorE1(event.currentTarget); + setLockCompoundsDialogAnchorE1(anchorEl); dispatch(setIsOpenLockVisibleCompoundsDialogLocal(true)); } else { - const refNext = ref.current.nextSibling; + let refNext = ref.current.nextSibling; scrollToElement(refNext); let nextItem = (nextItemData.hasOwnProperty('molecule') && nextItemData.molecule) || nextItemData; - const nextDatasetID = (nextItemData.hasOwnProperty('datasetID') && nextItemData.datasetID) || datasetID; - if (dispatch(isDatasetCompoundLocked(nextDatasetID, nextItem.id))) { - nextItem = dispatch(getFirstUnlockedCompoundAfter(nextDatasetID, nextItem.id)); + let nextDatasetID = (nextItemData.hasOwnProperty('datasetID') && nextItemData.datasetID) || datasetID; + if (dispatch(isCompoundLocked(nextDatasetID, nextItem)) || isCompoundFromVectorSelector(nextItem)) { + const unlockedCmp = dispatch(getFirstUnlockedSelectedCompoundAfter(nextDatasetID, nextItem.id)); + if (!unlockedCmp) { + return; + } + nextItem = unlockedCmp.molecule; + nextDatasetID = unlockedCmp.datasetID; + } + if (getNode) { + refNext = getNode(nextItem.id); } const moleculeTitleNext = nextItem && nextItem.name; @@ -668,29 +695,82 @@ const DatasetMoleculeView = memo( } dispatch( - moveDatasetMoleculeUpDown(stage, datasetID, data, nextDatasetID, nextItem, dataValue, ARROW_TYPE.DOWN) + moveSelectedDatasetMoleculeUpDown( + stage, + datasetID, + data, + nextDatasetID, + nextItem, + dataValue, + ARROW_TYPE.DOWN + ) ); } }; - const handleClickOnUpArrow = async event => { - const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedCompounds(datasetID, currentID)); - if (unlockedVisibleCompounds?.length > 0 && askLockCompoundsQuestion) { + const handleClickOnDownArrow = async event => { + if (inSelectedCompoundsList) { + moveDownSelectedCompound(event.currentTarget); + } else { + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedCompounds(datasetID, currentID)); + if (unlockedVisibleCompounds?.length > 0 && askLockCompoundsQuestion) { + dispatch(setCmpForLocalLockVisibleCompoundsDialog(data)); + setLockCompoundsDialogAnchorE1(event.currentTarget); + dispatch(setIsOpenLockVisibleCompoundsDialogLocal(true)); + } else { + let refNext = ref.current.nextSibling; + scrollToElement(refNext); + + let nextItem = (nextItemData.hasOwnProperty('molecule') && nextItemData.molecule) || nextItemData; + const nextDatasetID = (nextItemData.hasOwnProperty('datasetID') && nextItemData.datasetID) || datasetID; + if (dispatch(isDatasetCompoundLocked(nextDatasetID, nextItem.id))) { + nextItem = dispatch(getFirstUnlockedCompoundAfter(nextDatasetID, nextItem.id)); + } + if (getNode) { + refNext = getNode(nextItem.id); + } + const moleculeTitleNext = nextItem && nextItem.name; + + let dataValue = { colourToggle, isLigandOn, isProteinOn, isComplexOn, isSurfaceOn }; + + dispatch(setCrossReferenceCompoundName(moleculeTitleNext)); + if (setRef && ref.current) { + setRef(refNext); + } + + dispatch( + moveDatasetMoleculeUpDown(stage, datasetID, data, nextDatasetID, nextItem, dataValue, ARROW_TYPE.DOWN) + ); + } + } + }; + + const moveUpSelectedCompound = anchorEl => { + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedSelectedCompounds(datasetID, currentID)); + if (unlockedVisibleCompounds?.length > 0 && askLockSelectedCompoundsQuestion) { dispatch(setCmpForLocalLockVisibleCompoundsDialog(data)); - setLockCompoundsDialogAnchorE1(event.currentTarget); + setLockCompoundsDialogAnchorE1(anchorEl); dispatch(setIsOpenLockVisibleCompoundsDialogLocal(true)); } else { - const refPrevious = ref.current.previousSibling; + let refPrevious = ref.current.previousSibling; scrollToElement(refPrevious); let previousItem = (previousItemData.hasOwnProperty('molecule') && previousItemData.molecule) || previousItemData; - const previousDatasetID = + let previousDatasetID = (previousItemData.hasOwnProperty('datasetID') && previousItemData.datasetID) || datasetID; - if (dispatch(isDatasetCompoundLocked(previousDatasetID, previousItem.id))) { - previousItem = dispatch(getFirstUnlockedCompoundBefore(previousDatasetID, previousItem.id)); + if (dispatch(isCompoundLocked(previousDatasetID, previousItem))) { + const unlockedCmp = dispatch(getFirstUnlockedSelectedCompoundBefore(previousDatasetID, previousItem.id)); + if (!unlockedCmp) { + return; + } + previousItem = unlockedCmp.molecule; + previousDatasetID = unlockedCmp.datasetID; + } + if (getNode) { + refPrevious = getNode(previousItem.id); } - const moleculeTitlePrev = previousItem && previousItem.name; + const moleculeTitlePrev = previousItem && previousDatasetID.name; let dataValue = { colourToggle, isLigandOn, isProteinOn, isComplexOn, isSurfaceOn }; @@ -700,11 +780,66 @@ const DatasetMoleculeView = memo( } dispatch( - moveDatasetMoleculeUpDown(stage, datasetID, data, previousDatasetID, previousItem, dataValue, ARROW_TYPE.UP) + moveSelectedDatasetMoleculeUpDown( + stage, + datasetID, + data, + previousDatasetID, + previousItem, + dataValue, + ARROW_TYPE.UP + ) ); } }; + const handleClickOnUpArrow = async event => { + if (inSelectedCompoundsList) { + moveUpSelectedCompound(event.currentTarget); + } else { + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedCompounds(datasetID, currentID)); + if (unlockedVisibleCompounds?.length > 0 && askLockCompoundsQuestion) { + dispatch(setCmpForLocalLockVisibleCompoundsDialog(data)); + setLockCompoundsDialogAnchorE1(event.currentTarget); + dispatch(setIsOpenLockVisibleCompoundsDialogLocal(true)); + } else { + let refPrevious = ref.current.previousSibling; + scrollToElement(refPrevious); + + let previousItem = + (previousItemData.hasOwnProperty('molecule') && previousItemData.molecule) || previousItemData; + const previousDatasetID = + (previousItemData.hasOwnProperty('datasetID') && previousItemData.datasetID) || datasetID; + if (dispatch(isDatasetCompoundLocked(previousDatasetID, previousItem.id))) { + previousItem = dispatch(getFirstUnlockedCompoundBefore(previousDatasetID, previousItem.id)); + } + if (getNode) { + refPrevious = getNode(previousItem.id); + } + const moleculeTitlePrev = previousItem && previousItem.name; + + let dataValue = { colourToggle, isLigandOn, isProteinOn, isComplexOn, isSurfaceOn }; + + dispatch(setCrossReferenceCompoundName(moleculeTitlePrev)); + if (setRef && ref.current) { + setRef(refPrevious); + } + + dispatch( + moveDatasetMoleculeUpDown( + stage, + datasetID, + data, + previousDatasetID, + previousItem, + dataValue, + ARROW_TYPE.UP + ) + ); + } + } + }; + const moleculeTitle = data && data.name; const datasetTitle = datasets?.find(item => `${item.id}` === `${datasetID}`)?.title; @@ -744,26 +879,30 @@ const DatasetMoleculeView = memo( anchorEl={lockCompoundsDialogAnchorE1} datasetId={datasetID} currentCmp={data} + isSelectedCompounds={inSelectedCompoundsList} /> )} {/*Site number*/} - { - const result = e.target.checked; - if (result) { - dispatch(appendCompoundToSelectedCompoundsByDataset(datasetID, currentID, moleculeTitle)); - } else { - dispatch(removeCompoundFromSelectedCompoundsByDataset(datasetID, currentID, moleculeTitle)); - dispatch(deselectVectorCompound(data)); - } - }} - /> + {!isCompoundFromVectorSelector(data) && ( + { + const result = e.target.checked; + if (result) { + dispatch(appendCompoundToSelectedCompoundsByDataset(datasetID, currentID, moleculeTitle)); + } else { + dispatch(removeCompoundFromSelectedCompoundsByDataset(datasetID, currentID, moleculeTitle)); + dispatch(deselectVectorCompound(data)); + } + }} + /> + )} {index + 1}. diff --git a/js/components/datasets/lockVisibleCompoundsDialog.js b/js/components/datasets/lockVisibleCompoundsDialog.js index 5cf01b898..af986d62e 100644 --- a/js/components/datasets/lockVisibleCompoundsDialog.js +++ b/js/components/datasets/lockVisibleCompoundsDialog.js @@ -5,10 +5,16 @@ import { Close } from '@material-ui/icons'; import { useDispatch } from 'react-redux'; import { setAskLockCompoundsQuestion, + setAskLockSelectedCompoundsQuestion, setIsOpenLockVisibleCompoundsDialogGlobal, setIsOpenLockVisibleCompoundsDialogLocal } from './redux/actions'; -import { getAllVisibleButNotLockedCompounds, lockCompounds } from './redux/dispatchActions'; +import { + getAllVisibleButNotLockedCompounds, + getAllVisibleButNotLockedSelectedCompounds, + lockCompounds, + lockSelectedCompounds +} from './redux/dispatchActions'; const useStyles = makeStyles(theme => ({ paper: { @@ -27,76 +33,99 @@ const useStyles = makeStyles(theme => ({ } })); -export const LockVisibleCompoundsDialog = forwardRef(({ open = false, anchorEl, datasetId, currentCmp }, ref) => { - const id = open ? 'simple-popover-lock-visible-compounds' : undefined; - const classes = useStyles(); - const dispatch = useDispatch(); +export const LockVisibleCompoundsDialog = forwardRef( + ({ open = false, anchorEl, datasetId, currentCmp, isSelectedCompounds = false }, ref) => { + const id = open ? 'simple-popover-lock-visible-compounds' : undefined; + const classes = useStyles(); + const dispatch = useDispatch(); - const handleYesClick = () => { - if (currentCmp) { - let cmpsToLock = dispatch(getAllVisibleButNotLockedCompounds(datasetId)); - if (cmpsToLock && cmpsToLock.length > 0) { - dispatch(lockCompounds(datasetId, cmpsToLock, currentCmp.id)); - } - } else { - let cmpsToLock = dispatch(getAllVisibleButNotLockedCompounds(datasetId)); - //we need to skip first element if we came here from global arrows - const firstCmpId = cmpsToLock[0]; - if (cmpsToLock && cmpsToLock.length > 0) { - dispatch(lockCompounds(datasetId, cmpsToLock, firstCmpId)); + const handleYesClick = () => { + if (!isSelectedCompounds) { + if (currentCmp) { + let cmpsToLock = dispatch(getAllVisibleButNotLockedCompounds(datasetId)); + if (cmpsToLock && cmpsToLock.length > 0) { + dispatch(lockCompounds(datasetId, cmpsToLock, currentCmp.id)); + } + } else { + let cmpsToLock = dispatch(getAllVisibleButNotLockedCompounds(datasetId)); + //we need to skip first element if we came here from global arrows + if (cmpsToLock && cmpsToLock.length > 0) { + const firstCmpId = cmpsToLock[0]; + dispatch(lockCompounds(datasetId, cmpsToLock, firstCmpId)); + } + } + } else { + if (currentCmp) { + let cmpsToLock = dispatch(getAllVisibleButNotLockedSelectedCompounds()); + if (cmpsToLock && cmpsToLock.length > 0) { + dispatch(lockSelectedCompounds(cmpsToLock, currentCmp.id)); + } + } else { + let cmpsToLock = dispatch(getAllVisibleButNotLockedSelectedCompounds()); + if (cmpsToLock && cmpsToLock.length > 0) { + const firstCmpId = cmpsToLock[0]; + dispatch(lockSelectedCompounds(cmpsToLock, firstCmpId)); + } + } } - } - dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); - dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); - }; + dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); + dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); + }; - const handleNoClick = () => { - dispatch(setAskLockCompoundsQuestion(false)); - dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); - dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); - }; + const handleNoClick = () => { + if (!isSelectedCompounds) { + dispatch(setAskLockCompoundsQuestion(false)); + } else { + dispatch(setAskLockSelectedCompoundsQuestion(false)); + } + dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); + dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); + }; - return ( - - - { - dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); - dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); - }} - > - - - - ]} - > - - - Do you want to lock visible compounds? Otherwise they will disapear from the view. - - + return ( + + + { + dispatch(setIsOpenLockVisibleCompoundsDialogLocal(false)); + dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(false)); + }} + > + + + + ]} + > + - + + Do you want to lock visible compounds? Otherwise they will disapear from the view. + - - + + + + + + + - - - - ); -}); + + + ); + } +); diff --git a/js/components/datasets/redux/actions.js b/js/components/datasets/redux/actions.js index fdf803108..08329a9f8 100644 --- a/js/components/datasets/redux/actions.js +++ b/js/components/datasets/redux/actions.js @@ -537,3 +537,13 @@ export const setEditedColorGroup = colorGroup => ({ type: constants.SET_EDITED_COLOR_GROUP, colorGroup: colorGroup }); + +export const setSelectedCompoundsList = compoundsList => ({ + type: constants.SET_SELECTED_COMPOUNDS_LIST, + compoundsList: compoundsList +}); + +export const setAskLockSelectedCompoundsQuestion = askLockCompoundsQuestion => ({ + type: constants.SET_ASK_LOCK_SELECTED_COMPOUNDS_QUESTION, + askLockCompoundsQuestion: askLockCompoundsQuestion +}); diff --git a/js/components/datasets/redux/constants.js b/js/components/datasets/redux/constants.js index f3cefcc80..40e44c20d 100644 --- a/js/components/datasets/redux/constants.js +++ b/js/components/datasets/redux/constants.js @@ -104,7 +104,10 @@ export const constants = { SET_IS_OPEN_LOCK_VISIBLE_COMPOUNDS_DIALOG_LOCAL: prefix + 'SET_IS_OPEN_LOCK_VISIBLE_COMPOUNDS_DIALOG_LOCAL', SET_CMP_FOR_LOCAL_LOCK_VISIBLE_COMPOUNDS_DIALOG: prefix + 'SET_CMP_FOR_LOCAL_LOCK_VISIBLE_COMPOUNDS_DIALOG', SET_ASK_LOCK_COMPOUNDS_QUESTION: prefix + 'SET_ASK_LOCK_COMPOUNDS_QUESTION', - SET_EDITED_COLOR_GROUP: prefix + 'SET_EDITED_COLOR_GROUP' + SET_EDITED_COLOR_GROUP: prefix + 'SET_EDITED_COLOR_GROUP', + + SET_SELECTED_COMPOUNDS_LIST: prefix + 'SET_SELECTED_COMPOUNDS_LIST', + SET_ASK_LOCK_SELECTED_COMPOUNDS_QUESTION: prefix + 'SET_ASK_LOCK_SELECTED_COMPOUNDS_QUESTION' }; export const COUNT_OF_VISIBLE_SCORES = 7; diff --git a/js/components/datasets/redux/dispatchActions.js b/js/components/datasets/redux/dispatchActions.js index 0536ad581..18b209d84 100644 --- a/js/components/datasets/redux/dispatchActions.js +++ b/js/components/datasets/redux/dispatchActions.js @@ -73,6 +73,8 @@ import { getRepresentationsByType } from '../../nglView/generatingObjects'; import { selectAllMoleculeList } from '../../preview/molecule/redux/selectors'; import { getCompoundById } from '../../../reducers/tracking/dispatchActionsSwitchSnapshot'; import { getRandomColor } from '../../preview/molecule/utils/color'; +import { BreakfastDiningOutlined } from '@mui/icons-material'; +import { isCompoundFromVectorSelector } from '../../preview/compounds/redux/dispatchActions'; export const initializeDatasetFilter = datasetID => (dispatch, getState) => { const state = getState(); @@ -544,7 +546,7 @@ export const autoHideDatasetDialogsOnScroll = ({ inspirationDialogRef, crossRefe currentBoundingClientRectInspiration.y !== 0 ) { if ( - currentBoundingClientRectInspiration.top < scrollBarBoundingClientRect.top || + Math.round(currentBoundingClientRectInspiration.top) < Math.round(scrollBarBoundingClientRect.top) || Math.abs(scrollBarBoundingClientRect.bottom - currentBoundingClientRectInspiration.top) < 42 ) { dispatch(setIsOpenInspirationDialog(false)); @@ -558,7 +560,7 @@ export const autoHideDatasetDialogsOnScroll = ({ inspirationDialogRef, crossRefe currentBoundingClientRectCrossReference.y !== 0 ) { if ( - currentBoundingClientRectCrossReference.top < scrollBarBoundingClientRect.top || + Math.round(currentBoundingClientRectCrossReference.top) < Math.round(scrollBarBoundingClientRect.top) || Math.abs(scrollBarBoundingClientRect.bottom - currentBoundingClientRectCrossReference.top) < 42 ) { dispatch(resetCrossReferenceDialog()); @@ -732,11 +734,26 @@ const moveSelectedDatasetMoleculeInspirationsSettings = (data, newItemData, stag ); }; +export const lockSelectedCompounds = (selectedCompounds, skipCmpId = 0) => (dispatch, getState) => { + let filteredCompounds = [...selectedCompounds]; + if (skipCmpId) { + filteredCompounds = selectedCompounds.filter(item => item.molecule.id !== skipCmpId); + } + + filteredCompounds?.forEach(item => { + const datasetID = item.datasetID; + const molecule = item.molecule; + const moleculeID = molecule.id; + const moleculeName = molecule.name; + dispatch(appendCompoundToSelectedCompoundsByDataset(datasetID, moleculeID, moleculeName)); + }); +}; + export const lockCompounds = (datasetID, compoundIds, skipCmpId = 0) => (dispatch, getState) => { const state = getState(); const compounds = state.datasetsReducers.moleculeLists[datasetID]; - let filteredCompounds = []; + let filteredCompounds = [...compoundIds]; if (skipCmpId) { filteredCompounds = compoundIds.filter(item => item !== skipCmpId); } @@ -761,6 +778,56 @@ const mergeCompoundIdsList = (compoundIdsList, subList) => { return result; }; +export const isCompoundLocked = (datasetID, compound) => (dispatch, getState) => { + const state = getState(); + const lockedCompounds = state.datasetsReducers.selectedCompoundsByDataset[datasetID] || []; + return lockedCompounds.includes(compound.id); +}; + +export const getAllVisibleButNotLockedSelectedCompounds = (skipDatasetID = '', skipCmpId = 0) => ( + dispatch, + getState +) => { + let result = []; + + const state = getState(); + const selectedCompounds = state.datasetsReducers.selectedCompounds || []; + + selectedCompounds.forEach(item => { + const datasetID = item.datasetID; + const molecule = item.molecule; + if (datasetID !== skipDatasetID || skipCmpId !== molecule.id) { + const isLocked = dispatch(isCompoundLocked(datasetID, molecule)); + if (!isLocked) { + let isVisible = dispatch(isCompoundVisible(datasetID, molecule.id)); + // isVisible |= state.datasetsReducers.ligandLists[datasetID].includes(molecule.id); + // isVisible |= state.datasetsReducers.proteinLists[datasetID].includes(molecule.id); + // isVisible |= state.datasetsReducers.complexLists[datasetID].includes(molecule.id); + // isVisible |= state.datasetsReducers.surfaceLists[datasetID].includes(molecule.id); + + if (isVisible) { + result.push(item); + } + } + } + }); + + return result; +}; + +export const isCompoundVisible = (datasetID, compoundId) => (dispatch, getState) => { + let isVisible = false; + + const state = getState(); + + isVisible |= state.datasetsReducers.ligandLists[datasetID]?.includes(compoundId); + isVisible |= state.datasetsReducers.proteinLists[datasetID]?.includes(compoundId); + isVisible |= state.datasetsReducers.complexLists[datasetID]?.includes(compoundId); + isVisible |= state.datasetsReducers.surfaceLists[datasetID]?.includes(compoundId); + + return isVisible; +}; + export const getAllVisibleButNotLockedCompounds = (datasetID, skipCmpId = 0) => (dispatch, getState) => { let result = []; @@ -821,6 +888,52 @@ export const getFirstUnlockedCompoundAfter = (datasetID, compoundID) => (dispatc return firstUnlockedCompound; }; +export const getFirstUnlockedSelectedCompoundAfter = (datasetID, compoundID) => (dispatch, getState) => { + const state = getState(); + + const compounds = state.datasetsReducers.selectedCompounds; + const currentItemIndex = compounds.findIndex(item => item.datasetID === datasetID && item.molecule.id === compoundID); + + let firstUnlockedCompound = null; + for (let i = currentItemIndex + 1; i < compounds.length; i++) { + const compound = compounds[i]; + if ( + !dispatch(isCompoundLocked(compound.datasetID, compound.molecule)) && + !isCompoundFromVectorSelector(compound.molecule) + ) { + firstUnlockedCompound = compound; + break; + } else { + continue; + } + } + + return firstUnlockedCompound; +}; + +export const getFirstUnlockedSelectedCompoundBefore = (datasetID, compoundID) => (dispatch, getState) => { + const state = getState(); + + const compounds = state.datasetsReducers.selectedCompounds; + const currentItemIndex = compounds.findIndex(item => item.datasetID === datasetID && item.molecule.id === compoundID); + + let firstUnlockedCompound = null; + for (let i = currentItemIndex - 1; i >= 0; i--) { + const compound = compounds[i]; + if ( + !dispatch(isCompoundLocked(compound.datasetID, compound.molecule)) && + !isCompoundFromVectorSelector(compound.molecule) + ) { + firstUnlockedCompound = compound; + break; + } else { + continue; + } + } + + return firstUnlockedCompound; +}; + export const getFirstUnlockedCompoundBefore = (datasetID, compoundID) => (dispatch, getState) => { const state = getState(); @@ -836,6 +949,53 @@ export const getFirstUnlockedCompoundBefore = (datasetID, compoundID) => (dispat return firstUnlockedCompound; }; +export const moveSelectedDatasetMoleculeUpDown = ( + stage, + datasetID, + item, + newItemDatasetID, + newItem, + data, + direction +) => async (dispatch, getState) => { + const state = getState(); + const allInspirations = state.datasetsReducers.allInspirations; + const objectsInView = state.nglReducers.objectsInView; + const selectedCompounds = state.datasetsReducers.selectedCompounds; + let lockedCompounds = {}; + selectedCompounds.forEach(item => { + if (dispatch(isCompoundLocked(item.datasetID, item.molecule))) { + if (lockedCompounds.hasOwnProperty(item.datasetID)) { + lockedCompounds[item.datasetID].push(item.molecule.id); + } else { + lockedCompounds[item.datasetID] = [item.molecule.id]; + } + } + }); + + //also skip next item + if (lockedCompounds.hasOwnProperty(newItemDatasetID)) { + lockedCompounds[newItemDatasetID].push(newItem.id); + } else { + lockedCompounds[newItemDatasetID] = [newItem.id]; + } + const dataValue = { ...data, objectsInView }; + + // ???? + // dispatch(setArrowUpDown(datasetID, item, newItem, direction, dataValue)); + + const inspirations = getInspirationsForMol(allInspirations, datasetID, newItem.id); + dispatch(setInspirationMoleculeDataList(inspirations)); + dispatch(clearCompoundView(newItem, datasetID, stage, true)); + await Promise.all([ + dispatch(moveSelectedMoleculeSettings(stage, item, newItem, newItemDatasetID, datasetID, dataValue, true)), + dispatch(moveSelectedDatasetMoleculeInspirationsSettings(item, newItem, stage, true)) + ]); + + dispatch(removeSelectedDatasetMolecules(stage, true, { ...lockedCompounds })); + dispatch(removeSelectedTypesOfDatasetInspirations([newItem], stage, true, datasetID)); +}; + /** * Performance optimization for datasetMoleculeView. Gets objectsInView and passes it to further dispatch requests. * It wouldnt do anything else in moleculeView. Also this chains the above 3 methods which were before passed to @@ -849,7 +1009,7 @@ export const moveDatasetMoleculeUpDown = (stage, datasetID, item, newItemDataset const state = getState(); const allInspirations = state.datasetsReducers.allInspirations; const objectsInView = state.nglReducers.objectsInView; - const lockedCompounds = state.datasetsReducers.selectedCompoundsByDataset[datasetID] ?? []; + let lockedCompounds = state.datasetsReducers.selectedCompoundsByDataset[datasetID] ?? []; const dataValue = { ...data, objectsInView }; diff --git a/js/components/datasets/redux/reducer.js b/js/components/datasets/redux/reducer.js index 290561220..3733ead65 100644 --- a/js/components/datasets/redux/reducer.js +++ b/js/components/datasets/redux/reducer.js @@ -50,6 +50,8 @@ export const INITIAL_STATE = { selectedCompoundsByDataset: {}, // map of $datasetID and its list of moleculeID compoundColorByDataset: {}, // map of $datasetID and its list of moleculeID + selectedCompounds: [], // list of selected compounds + selectedColorsInFilter: { [compoundsColors.blue.key]: compoundsColors.blue.key, [compoundsColors.red.key]: compoundsColors.red.key, @@ -76,7 +78,8 @@ export const INITIAL_STATE = { isLockVisibleCompoundsDialogOpenLocal: false, cmpForLocalLockVisibleCompoundsDialog: null, askLockCompoundsQuestion: true, - editedColorGroup: null + editedColorGroup: null, + askLockSelectedCompoundsQuestion: true }; /** @@ -645,6 +648,14 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { return { ...state, askLockCompoundsQuestion: action.askLockCompoundsQuestion }; } + case constants.SET_ASK_LOCK_SELECTED_COMPOUNDS_QUESTION: { + return { ...state, askLockSelectedCompoundsQuestion: action.askLockCompoundsQuestion }; + } + + case constants.SET_SELECTED_COMPOUNDS_LIST: { + return { ...state, selectedCompounds: action.compoundsList }; + } + case constants.RESET_DATASETS_STATE_ON_SNAPSHOT_CHANGE: { const { datasets, diff --git a/js/components/datasets/selectedCompoundsList.js b/js/components/datasets/selectedCompoundsList.js index 15d10e3a6..7a529e923 100644 --- a/js/components/datasets/selectedCompoundsList.js +++ b/js/components/datasets/selectedCompoundsList.js @@ -1,4 +1,4 @@ -import React, { memo, useContext, useEffect, useRef, useState } from 'react'; +import React, { memo, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Panel } from '../common/Surfaces/Panel'; import { CircularProgress, @@ -11,7 +11,7 @@ import { InputAdornment, IconButton } from '@material-ui/core'; -import { CloudDownload, Edit } from '@material-ui/icons'; +import { ArrowDownward, ArrowUpward, CloudDownload, Edit } from '@material-ui/icons'; import { useDispatch, useSelector } from 'react-redux'; import { getMoleculesObjectIDListOfCompoundsToBuy, @@ -23,9 +23,22 @@ import { import InfiniteScroll from 'react-infinite-scroller'; import DatasetMoleculeView from './datasetMoleculeView'; import { InspirationDialog } from './inspirationDialog'; -import { setIsOpenInspirationDialog } from './redux/actions'; +import { + setCrossReferenceCompoundName, + setIsOpenInspirationDialog, + setIsOpenLockVisibleCompoundsDialogGlobal, + setSelectedCompoundsList +} from './redux/actions'; import { CrossReferenceDialog } from './crossReferenceDialog'; -import { autoHideDatasetDialogsOnScroll, resetCrossReferenceDialog } from './redux/dispatchActions'; +import { + autoHideDatasetDialogsOnScroll, + getAllVisibleButNotLockedSelectedCompounds, + isCompoundLocked, + isCompoundVisible, + moveDatasetMoleculeUpDown, + moveSelectedDatasetMoleculeUpDown, + resetCrossReferenceDialog +} from './redux/dispatchActions'; import { NglContext } from '../nglView/nglProvider'; import FileSaver from 'file-saver'; import JSZip from 'jszip'; @@ -48,6 +61,14 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; import { compoundsColors } from '../preview/compounds/redux/constants'; import classNames from 'classnames'; import { fabClasses } from '@mui/material'; +import { is } from 'date-fns/locale'; +import { LockVisibleCompoundsDialog } from './lockVisibleCompoundsDialog'; +import { BreakfastDiningOutlined } from '@mui/icons-material'; +import { getRandomColor } from '../preview/molecule/utils/color'; +import { ARROW_TYPE, VIEWS } from '../../constants/constants'; +import { useScrollToCompound } from './useScrollToCompound'; +import useDisableNglControlButtons from '../preview/molecule/useDisableNglControlButtons'; +import useDisableDatasetNglControlButtons from './useDisableDatasetNglControlButtons'; const useStyles = makeStyles(theme => ({ container: { @@ -84,11 +105,19 @@ const useStyles = makeStyles(theme => ({ backgroundColor: compoundsColors.apricot.color }, textField: { - // marginLeft: theme.spacing(1), + marginLeft: theme.spacing(0.5), // marginRight: theme.spacing(1), - width: 70, + width: 90, + borderRadius: 10, + '& .MuiFormLabel-root': { paddingLeft: theme.spacing(1) + }, + '& .MuiInput-underline:before': { + borderBottom: '0px solid' + }, + '& .MuiInput-underline:after': { + borderBottom: '0px solid' } }, selectedInput: { @@ -103,7 +132,34 @@ const useStyles = makeStyles(theme => ({ }, editClassNameIconSelected: { padding: '0px', - color: theme.palette.primary.main + color: 'red' + }, + arrowsHighlight: { + borderColor: theme.palette.primary.main, + border: 'solid 2px', + backgroundColor: theme.palette.primary.main + }, + arrow: { + width: 20, + height: 25, + color: 'white', + stroke: 'white', + strokeWidth: 2 + }, + iconButton: { + padding: 0, + marginTop: '2px' + }, + invisArrow: { + width: 20, + height: 25, + visibility: 'hidden' + }, + arrows: { + height: '100%', + border: 'solid 1px', + borderColor: theme.palette.background.divider, + borderStyle: 'solid solid solid solid' } })); @@ -151,8 +207,26 @@ export const SelectedCompoundList = memo(() => { selectedMolecules = [...selectedMolecules, ...molsOfDataset.filter(mol => datasetCmpsToBuy?.includes(mol.id))]; }); + const { addMoleculeViewRef, setScrollToMoleculeId, getNode } = useScrollToCompound(); + const currentCompoundClass = useSelector(state => state.previewReducers.compounds.currentCompoundClass); + // const groupDatasetsNglControlButtonsDisabledState = useDisableDatasetNglControlButtons( + // lockedMolecules.map(cid => ({ datasetID, molecule: getCompoundForId(cid) })) + // ); + + const askLockSelectedCompoundsQuestion = useSelector( + state => state.datasetsReducers.askLockSelectedCompoundsQuestion + ); + + const lockVisibleCompoundsDialogRef = useRef(); + const isLockVisibleCompoundsDialogOpenGlobal = useSelector( + state => state.datasetsReducers.isLockVisibleCompoundsDialogOpenGlobal + ); + + const { getNglView } = useContext(NglContext); + const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; + const blueInput = useSelector(state => state.previewReducers.compounds[compoundsColors.blue.key]); const redInput = useSelector(state => state.previewReducers.compounds[compoundsColors.red.key]); const greenInput = useSelector(state => state.previewReducers.compounds[compoundsColors.green.key]); @@ -167,10 +241,54 @@ export const SelectedCompoundList = memo(() => { [compoundsColors.apricot.key]: apricotInput }; + const inputRefs = { + [compoundsColors.blue.key]: useRef(), + [compoundsColors.red.key]: useRef(), + [compoundsColors.green.key]: useRef(), + [compoundsColors.purple.key]: useRef(), + [compoundsColors.apricot.key]: useRef() + }; + const compoundColors = useSelector(state => state.datasetsReducers.compoundColorByDataset); const colorFilterSettings = useSelector(state => state.datasetsReducers.selectedColorsInFilter); + const [lockCompoundsDialogAnchorE1, setLockCompoundsDialogAnchorE1] = useState(null); + + //we need to add also lists to dependancy array because isCompoundVisible depends on them + useEffect(() => { + dispatch(setSelectedCompoundsList(moleculesObjectIDListOfCompoundsToBuy)); + }, [moleculesObjectIDListOfCompoundsToBuy, dispatch]); + + let areArrowsVisible = useMemo(() => { + let result = false; + if (moleculesObjectIDListOfCompoundsToBuy) { + for (let i = 0; i < moleculesObjectIDListOfCompoundsToBuy.length; i++) { + const cmp = moleculesObjectIDListOfCompoundsToBuy[i]; + const dataset = cmp.datasetID; + const molecule = cmp.molecule; + const isVisible = dispatch(isCompoundVisible(dataset, molecule.id)); + if (isVisible) { + result = true; + break; + } + } + } + return result; + }, [dispatch, moleculesObjectIDListOfCompoundsToBuy, ligandList, proteinList, complexList, surfaceList]); + // if (!moleculesObjectIDListOfCompoundsToBuy) { + // for (let i = 0; i < moleculesObjectIDListOfCompoundsToBuy.length; i++) { + // const cmp = moleculesObjectIDListOfCompoundsToBuy[i]; + // const dataset = cmp.datasetID; + // const molecule = cmp.molecule; + // const isVisible = dispatch(isCompoundVisible(dataset, molecule.id)); + // if (isVisible) { + // areArrowsVisible = true; + // break; + // } + // } + // } + useEffect(() => { return () => { dispatch(setIsOpenInspirationDialog(false)); @@ -444,6 +562,142 @@ export const SelectedCompoundList = memo(() => { FileSaver.saveAs(zipBlob, 'selectedCompounds.zip'); }; + const getFirstItemForIterationStart = () => { + let result = null; + for (let i = 0; i < moleculesObjectIDListOfCompoundsToBuy.length; i++) { + const cmp = moleculesObjectIDListOfCompoundsToBuy[i]; + if (!dispatch(isCompoundLocked(cmp.datasetID, cmp.molecule)) && !isCompoundFromVectorSelector(cmp.molecule)) { + const isVisible = dispatch(isCompoundVisible(cmp.datasetID, cmp.molecule.id)); + if (isVisible) { + result = cmp; + break; + } + } + } + + return result; + }; + + const getNextItemForIteration = currentItem => { + let result = null; + + const currentItemIndex = moleculesObjectIDListOfCompoundsToBuy.findIndex( + cmp => cmp.molecule.id === currentItem.molecule.id && cmp.datasetID === currentItem.datasetID + ); + for (let i = currentItemIndex + 1; i < moleculesObjectIDListOfCompoundsToBuy.length; i++) { + const cmp = moleculesObjectIDListOfCompoundsToBuy[i]; + if (!dispatch(isCompoundLocked(cmp.datasetID, cmp.molecule)) && !isCompoundFromVectorSelector(cmp.molecule)) { + result = cmp; + break; + } + } + + return result; + }; + + const getPrevItemForIteration = currentItem => { + let result = null; + + const currentItemIndex = moleculesObjectIDListOfCompoundsToBuy.findIndex( + cmp => cmp.molecule.id === currentItem.molecule.id && cmp.datasetID === currentItem.datasetID + ); + for (let i = currentItemIndex - 1; i >= 0; i--) { + const cmp = moleculesObjectIDListOfCompoundsToBuy[i]; + if (!dispatch(isCompoundLocked(cmp.datasetID, cmp.molecule)) && !isCompoundFromVectorSelector(cmp.molecule)) { + result = cmp; + break; + } + } + + return result; + }; + + const handleClickOnDownArrow = async event => { + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedSelectedCompounds()); + //one unlocked compound is what we want because it designate where the iteration will start + if (unlockedVisibleCompounds?.length > 1 && askLockSelectedCompoundsQuestion) { + setLockCompoundsDialogAnchorE1(event.currentTarget); + dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(true)); + } else { + const firstItem = getFirstItemForIterationStart(); + const nextItem = getNextItemForIteration(firstItem); + + if (firstItem && nextItem) { + const moleculeTitleNext = nextItem && nextItem.molecule?.name; + const node = getNode(nextItem.molecule?.id); + setScrollToMoleculeId(nextItem.molecule?.id); + + let dataValue = { + colourToggle: getRandomColor(firstItem.molecule), + isLigandOn: ligandList.includes(firstItem.molecule?.id), + isProteinOn: proteinList.includes(firstItem.molecule?.id), + isComplexOn: complexList.includes(firstItem.molecule?.id), + isSurfaceOn: surfaceList.includes(firstItem.molecule?.id) + }; + + dispatch(setCrossReferenceCompoundName(moleculeTitleNext)); + + if (node) { + setSelectedMoleculeRef(node); + } + dispatch( + moveSelectedDatasetMoleculeUpDown( + stage, + firstItem.datasetID, + firstItem.molecule, + nextItem.datasetID, + nextItem.molecule, + dataValue, + ARROW_TYPE.DOWN + ) + ); + } + } + }; + + const handleClickOnUpArrow = async event => { + const unlockedVisibleCompounds = dispatch(getAllVisibleButNotLockedSelectedCompounds()); + //one unlocked compound is what we want because it designate where the iteration will start + if (unlockedVisibleCompounds?.length > 1 && askLockSelectedCompoundsQuestion) { + setLockCompoundsDialogAnchorE1(event.currentTarget); + dispatch(setIsOpenLockVisibleCompoundsDialogGlobal(true)); + } else { + const firstItem = getFirstItemForIterationStart(); + const prevItem = getPrevItemForIteration(firstItem); + + if (firstItem && prevItem) { + const moleculeTitleNext = prevItem && prevItem.molecule?.name; + const node = getNode(prevItem.molecule?.id); + setScrollToMoleculeId(prevItem.molecule?.id); + + let dataValue = { + colourToggle: getRandomColor(firstItem.molecule), + isLigandOn: ligandList.includes(firstItem.molecule?.id), + isProteinOn: proteinList.includes(firstItem.molecule?.id), + isComplexOn: complexList.includes(firstItem.molecule?.id), + isSurfaceOn: surfaceList.includes(firstItem.molecule?.id) + }; + + dispatch(setCrossReferenceCompoundName(moleculeTitleNext)); + + if (node) { + setSelectedMoleculeRef(node); + } + dispatch( + moveSelectedDatasetMoleculeUpDown( + stage, + firstItem.datasetID, + firstItem.molecule, + prevItem.datasetID, + prevItem.molecule, + dataValue, + ARROW_TYPE.UP + ) + ); + } + } + }; + return ( { ref={inspirationDialogRef} /> )} + {askLockSelectedCompoundsQuestion && isLockVisibleCompoundsDialogOpenGlobal && ( + + )} {isOpenCrossReferenceDialog && ( )} - + {/* Selection */} {Object.keys(compoundsColors).map(item => ( <> - - dispatch(onClickFilterClassCheckBox(e))} - checked={colorFilterSettings.hasOwnProperty(item)} - > - + endAdornment: ( + dispatch(onStartEditColorClassName(e))} + onClick={e => { + dispatch(onStartEditColorClassName(e)); + inputRefs[item].current.focus(); + inputRefs[item].current.select(); + }} > + ), + startAdornment: ( + + dispatch(onClickFilterClassCheckBox(e))} + checked={colorFilterSettings.hasOwnProperty(item)} + > + ) }} autoComplete="off" + inputRef={inputRefs[item]} id={`${item}`} key={`CLASS_${item}`} variant="standard" @@ -519,7 +788,6 @@ export const SelectedCompoundList = memo(() => { classes[item], colorFilterSettings.hasOwnProperty(item) && classes.selectedInput )} - label={compoundsColors[item].text} onChange={e => dispatch(onChangeCompoundClassValue(e))} onKeyDown={e => dispatch(onKeyDownCompoundClass(e))} // onKeyDown={e => dispatch(onKeyDownFilterClass(e))} @@ -530,6 +798,44 @@ export const SelectedCompoundList = memo(() => { ))} + + + + + + + + + + + + + + {currentMolecules.length > 0 && ( @@ -593,6 +899,8 @@ export const SelectedCompoundList = memo(() => { } } } + + const isLocked = dispatch(isCompoundLocked(data.datasetID, data.molecule)); // } return ( isVisible && ( @@ -603,7 +911,8 @@ export const SelectedCompoundList = memo(() => { imageWidth={imgWidth} data={data.molecule} datasetID={data.datasetID} - setRef={setSelectedMoleculeRef} + // setRef={setSelectedMoleculeRef} + ref={addMoleculeViewRef} showCrossReferenceModal previousItemData={index > 0 && array[index - 1]} nextItemData={index < array?.length && array[index + 1]} @@ -612,12 +921,15 @@ export const SelectedCompoundList = memo(() => { C={complexList.includes(data.molecule.id)} S={surfaceList.includes(data.molecule.id)} V={false} - arrowsHidden + arrowsHidden={false} dragDropEnabled shoppingCartColors={shoppingCartColors} isAddedToShoppingCart={isAddedToShoppingCart} inSelectedCompoundsList colorButtonsEnabled={areColorButtonsEnabled} + isLocked={isLocked} + setRef={setSelectedMoleculeRef} + getNode={getNode} /> ) ); diff --git a/js/components/datasets/useScrollToCompound.js b/js/components/datasets/useScrollToCompound.js new file mode 100644 index 000000000..da333b329 --- /dev/null +++ b/js/components/datasets/useScrollToCompound.js @@ -0,0 +1,58 @@ +import { useCallback, useEffect, useState } from 'react'; + +export const useScrollToCompound = () => { + const [moleculeViewRefs, setMoleculeViewRefs] = useState({}); + const [scrollToMoleculeId, setScrollToMoleculeId] = useState(null); + + useEffect(() => { + if (scrollToMoleculeId !== null) { + const node = moleculeViewRefs[scrollToMoleculeId]; + if (node) { + setScrollToMoleculeId(null); + if (!elementIsVisibleInViewport(node)) { + setTimeout(() => { + node.scrollIntoView(); + }); + } + } + } + }, [moleculeViewRefs, scrollToMoleculeId]); + + const elementIsVisibleInViewport = (el, partiallyVisible = false) => { + const { top, left, bottom, right } = el.getBoundingClientRect(); + const { innerHeight, innerWidth } = window; + return partiallyVisible + ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && + ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) + : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth; + }; + + // Used to attach the ref of DOM nodes. + // const addMoleculeViewRef = useCallback((moleculeId, node) => { + // if (moleculeId && node) { + // setMoleculeViewRefs(prevRefs => ({ + // ...prevRefs, + // [moleculeId]: node + // })); + // } + // }, []); + + const addMoleculeViewRef = useCallback((moleculeId, node) => { + setMoleculeViewRefs(prevRefs => { + if (prevRefs.hasOwnProperty(moleculeId)) return prevRefs; + return { + ...prevRefs, + [moleculeId]: node + }; + }); + }, []); + + const getNode = useCallback( + molId => { + return moleculeViewRefs[molId]; + }, + [moleculeViewRefs] + ); + + return { addMoleculeViewRef, setScrollToMoleculeId, getNode }; +}; diff --git a/js/components/datasets/useScrollToSelected.js b/js/components/datasets/useScrollToSelected.js index e5c1db38d..e1c796277 100644 --- a/js/components/datasets/useScrollToSelected.js +++ b/js/components/datasets/useScrollToSelected.js @@ -97,12 +97,29 @@ export const useScrollToSelected = (datasetID, moleculesPerPage, setCurrentPage) }; // Used to attach the ref of DOM nodes. + // const addMoleculeViewRef = useCallback((moleculeId, node) => { + // setMoleculeViewRefs(prevRefs => ({ + // ...prevRefs, + // [moleculeId]: node + // })); + // }, []); + const addMoleculeViewRef = useCallback((moleculeId, node) => { - setMoleculeViewRefs(prevRefs => ({ - ...prevRefs, - [moleculeId]: node - })); + setMoleculeViewRefs(prevRefs => { + if (prevRefs.hasOwnProperty(moleculeId)) return prevRefs; + return { + ...prevRefs, + [moleculeId]: node + }; + }); }, []); - return { addMoleculeViewRef, setScrollToMoleculeId }; + const getNode = useCallback( + molId => { + return moleculeViewRefs[molId]; + }, + [moleculeViewRefs] + ); + + return { addMoleculeViewRef, setScrollToMoleculeId, getNode }; }; diff --git a/js/components/preview/compounds/compoundList.js b/js/components/preview/compounds/compoundList.js index f28e29df6..7177b990d 100644 --- a/js/components/preview/compounds/compoundList.js +++ b/js/components/preview/compounds/compoundList.js @@ -36,11 +36,19 @@ import classNames from 'classnames'; const useStyles = makeStyles(theme => ({ textField: { - // marginLeft: theme.spacing(1), + marginLeft: theme.spacing(0.5), // marginRight: theme.spacing(1), - width: 70, + width: 90, + borderRadius: 10, + '& .MuiFormLabel-root': { paddingLeft: theme.spacing(1) + }, + '& .MuiInput-underline:before': { + borderBottom: '0px solid' + }, + '& .MuiInput-underline:after': { + borderBottom: '0px solid' } }, selectedInput: { @@ -86,7 +94,7 @@ const useStyles = makeStyles(theme => ({ }, editClassNameIconSelected: { padding: '0px', - color: theme.palette.primary.main + color: 'red' } })); @@ -113,6 +121,14 @@ export const CompoundList = memo(() => { [compoundsColors.apricot.key]: apricotInput }; + const inputRefs = { + [compoundsColors.blue.key]: useRef(), + [compoundsColors.red.key]: useRef(), + [compoundsColors.green.key]: useRef(), + [compoundsColors.purple.key]: useRef(), + [compoundsColors.apricot.key]: useRef() + }; + const currentCompoundClass = useSelector(state => state.previewReducers.compounds.currentCompoundClass); const canLoadMoreCompounds = useSelector(state => getCanLoadMoreCompounds(state)); const currentVector = useSelector(state => state.selectionReducers.currentVector); @@ -131,35 +147,42 @@ export const CompoundList = memo(() => { {Object.keys(compoundsColors).map(item => ( <> - - dispatch(onChangeCompoundClassCheckbox(e))} - checked={currentCompoundClass === item} - > - + endAdornment: ( + dispatch(onStartEditColorClassName(e))} + onClick={e => { + dispatch(onStartEditColorClassName(e)); + inputRefs[item].current.focus(); + inputRefs[item].current.select(); + }} > + ), + startAdornment: ( + + dispatch(onChangeCompoundClassCheckbox(e))} + checked={currentCompoundClass === item} + > + ) }} autoComplete="off" + inputRef={inputRefs[item]} id={`${item}`} key={`CLASS_${item}`} variant="standard" @@ -168,7 +191,6 @@ export const CompoundList = memo(() => { classes[item], currentCompoundClass === item && classes.selectedInput )} - label={compoundsColors[item].text} onChange={e => dispatch(onChangeCompoundClassValue(e))} onKeyDown={e => dispatch(onKeyDownCompoundClass(e))} onClick={e => dispatch(onClickCompoundClass(e))} diff --git a/js/components/preview/compounds/redux/dispatchActions.js b/js/components/preview/compounds/redux/dispatchActions.js index ce02a4f22..461d5f408 100644 --- a/js/components/preview/compounds/redux/dispatchActions.js +++ b/js/components/preview/compounds/redux/dispatchActions.js @@ -299,7 +299,7 @@ export const prepareFakeFilterData = () => (dispatch, getState) => { }; export const isCompoundFromVectorSelector = data => { - if (data['index'] !== undefined) { + if (data && data['index'] !== undefined) { return true; } else { return false; diff --git a/js/components/preview/compounds/redux/reducer.js b/js/components/preview/compounds/redux/reducer.js index d37121b06..4a3490939 100644 --- a/js/components/preview/compounds/redux/reducer.js +++ b/js/components/preview/compounds/redux/reducer.js @@ -8,15 +8,16 @@ const defaultSelectedCmpdsClass = { [compoundsColors.apricot.key]: [] }; -const defaultCompoundsClasses = { - [compoundsColors.blue.key]: undefined, - [compoundsColors.red.key]: undefined, - [compoundsColors.green.key]: undefined, - [compoundsColors.purple.key]: undefined, - [compoundsColors.apricot.key]: undefined +const defaultCmpdsValues = { + [compoundsColors.blue.key]: compoundsColors.blue.key, + [compoundsColors.red.key]: compoundsColors.red.key, + [compoundsColors.green.key]: compoundsColors.green.key, + [compoundsColors.purple.key]: compoundsColors.purple.key, + [compoundsColors.apricot.key]: compoundsColors.apricot.key }; export const INITIAL_STATE = { + ...defaultCmpdsValues, currentPage: -1, compoundsPerPage: 20, /* currentCompounds: [{ @@ -27,7 +28,7 @@ export const INITIAL_STATE = { }] */ currentCompounds: [], currentCompoundClass: compoundsColors.blue.key, - ...defaultCompoundsClasses, + // ...defaultCompoundsClasses, selectedCompoundsClass: defaultSelectedCmpdsClass, highlightedCompoundId: null, showedCompoundList: [], @@ -50,7 +51,7 @@ export const RESET_STATE = { }] */ currentCompounds: [], currentCompoundClass: compoundsColors.blue.key, - ...defaultCompoundsClasses, + ...defaultCmpdsValues, selectedCompoundsClass: defaultSelectedCmpdsClass, highlightedCompoundId: null, showedCompoundList: [], @@ -92,7 +93,7 @@ export const compounds = (state = INITIAL_STATE, action = {}) => { }); case constants.RESET_COMPOUND_CLASSES: - return Object.assign({}, state, { ...defaultCompoundsClasses }); + return Object.assign({}, state, { ...defaultCmpdsValues }); case constants.SET_HIGHLIGHTED_COMPOUND_ID: return Object.assign({}, state, { highlightedCompoundId: action.payload }); @@ -155,9 +156,9 @@ export const compounds = (state = INITIAL_STATE, action = {}) => { return Object.assign({}, state, { selectedCompoundsClass: defaultSelectedCmpdsClass }); - - case constants.SET_SELECTED_COMPOUNDS: - return {...state, allSelectedCompounds: action.payload}; + + case constants.SET_SELECTED_COMPOUNDS: + return { ...state, allSelectedCompounds: action.payload }; case constants.RELOAD_REDUCER: return Object.assign({}, state, { ...action.payload }); diff --git a/package.json b/package.json index 44f0e1cd7..1a0ba3094 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fragalysis-frontend", - "version": "0.10.184", + "version": "0.10.185", "default_squonk_project": "project-8f32b412-8329-4469-a39c-8581efa93796", "description": "Frontend for fragalysis", "main": "webpack.config.js",