From 1c45038a42430f90f68430037027296021fe1974 Mon Sep 17 00:00:00 2001 From: Boris Kovar Date: Fri, 13 Oct 2023 11:03:49 +0200 Subject: [PATCH] - implemented #1173 also with save/restore and undo/redo functionality --- js/components/datasets/datasetMoleculeList.js | 127 ++++++++++++--- .../datasetMoleculeView.js | 9 +- js/components/datasets/redux/actions.js | 16 +- js/components/datasets/redux/constants.js | 2 + .../projects/redux/dispatchActions.js | 2 +- js/reducers/tracking/constants.js | 4 +- js/reducers/tracking/dispatchActions.js | 149 ++++++++++++++---- js/reducers/tracking/trackingActions.js | 28 ++++ 8 files changed, 278 insertions(+), 59 deletions(-) diff --git a/js/components/datasets/datasetMoleculeList.js b/js/components/datasets/datasetMoleculeList.js index 80b8cc2a6..d72fb36d1 100644 --- a/js/components/datasets/datasetMoleculeList.js +++ b/js/components/datasets/datasetMoleculeList.js @@ -14,7 +14,8 @@ import { TextField, Checkbox, InputAdornment, - setRef + setRef, + Box } from '@material-ui/core'; import React, { useState, useEffect, memo, useRef, useContext, useCallback, useMemo } from 'react'; import { useDispatch, useSelector, shallowEqual } from 'react-redux'; @@ -52,7 +53,11 @@ import { setIsOpenLockVisibleCompoundsDialogGlobal, setSearchStringOfCompoundSet, setCompoundToSelectedCompoundsByDataset, - setSelectAllButtonForDataset + setSelectAllButtonForDataset, + appendCompoundColorOfDataset, + appendColorToAllCompoundsOfDataset, + removeCompoundColorOfDataset, + removeColorFromAllCompoundsOfDataset } from './redux/actions'; import { DatasetFilter } from './datasetFilter'; import { FilterList, Link, DeleteForever, ArrowUpward, ArrowDownward, Edit } from '@material-ui/icons'; @@ -79,6 +84,7 @@ import { onStartEditColorClassName } from '../preview/compounds/redux/dispatchActions'; import { LockVisibleCompoundsDialog } from './lockVisibleCompoundsDialog'; +import { size } from 'lodash'; const useStyles = makeStyles(theme => ({ container: { @@ -163,6 +169,26 @@ const useStyles = makeStyles(theme => ({ contButtonsMargin: { margin: theme.spacing(1) / 2 }, + paintAllButton: { + minWidth: 'fit-content', + paddingLeft: theme.spacing(1) / 4, + paddingRight: theme.spacing(1) / 4, + paddingBottom: 0, + paddingTop: 0, + fontWeight: 'bold', + fontSize: 9, + borderRadius: 0, + borderColor: theme.palette.primary.main, + backgroundColor: 'white', + '&:hover': { + backgroundColor: 'white' + // color: theme.palette.primary.contrastText + }, + '&:disabled': { + borderRadius: 0, + borderColor: 'white' + } + }, contColButton: { minWidth: 'fit-content', paddingLeft: theme.spacing(1) / 4, @@ -808,6 +834,39 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { } }; + const isPaintOrUnpaintAll = () => { + let isPaint = true; + const compounds = Object.keys(compoundColors); + for (let i = 0; i < compounds.length; i++) { + const cmpId = compounds[i]; + const colors = compoundColors[cmpId]; + if (colors.some(c => c === currentCompoundClass)) { + isPaint = false; + break; + } + } + + return isPaint; + }; + + const paintAllCompounds = () => { + const paintAll = isPaintOrUnpaintAll(); + const cmpIds = joinedMoleculeLists.map(mol => mol.id); + if (paintAll) { + joinedMoleculeLists.forEach(molecule => { + const molName = molecule.name; + dispatch(appendCompoundColorOfDataset(datasetID, molecule.id, currentCompoundClass, molName, true)); + }); + dispatch(appendColorToAllCompoundsOfDataset(datasetID, currentCompoundClass, cmpIds)); + } else { + joinedMoleculeLists.forEach(molecule => { + const molName = molecule.name; + dispatch(removeCompoundColorOfDataset(datasetID, molecule.id, currentCompoundClass, molName, true)); + }); + dispatch(removeColorFromAllCompoundsOfDataset(datasetID, currentCompoundClass, cmpIds)); + } + }; + return ( { - { - - - + + + + + + + - - - } + /> + {isPaintOrUnpaintAll() ? 'Paint all' : 'Unpaint all'} + + + diff --git a/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js b/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js index cdac14817..f50ef195a 100644 --- a/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js +++ b/js/components/datasets/datasetMoleculeView/datasetMoleculeView.js @@ -78,6 +78,7 @@ import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart'; import RemoveShoppingCartIcon from '@mui/icons-material/RemoveShoppingCart'; import { compoundsColors } from '../../preview/compounds/redux/constants'; import { LockVisibleCompoundsDialog } from '../lockVisibleCompoundsDialog'; +import { fabClasses } from '@mui/material'; const useStyles = makeStyles(theme => ({ container: { @@ -639,10 +640,10 @@ const DatasetMoleculeView = memo( // if (shoppingCartColors?.length === 1) { // dispatch(removeMoleculeFromCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle)); // } - dispatch(removeCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, true)); + dispatch(removeCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, false)); } else { // dispatch(appendMoleculeToCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle)); - dispatch(appendCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, true)); + dispatch(appendCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, false)); } }; @@ -650,7 +651,7 @@ const DatasetMoleculeView = memo( if (shoppingCartColors?.length === 1) { dispatch(removeMoleculeFromCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle)); } - dispatch(removeCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, true)); + dispatch(removeCompoundColorOfDataset(datasetID, currentID, event.target.id, moleculeTitle, false)); }; const handleShoppingCartClick = () => { @@ -658,7 +659,7 @@ const DatasetMoleculeView = memo( // if (!isAddedToShoppingCart) { // dispatch(appendMoleculeToCompoundsOfDatasetToBuy(datasetID, currentID, moleculeTitle)); // } - dispatch(appendCompoundColorOfDataset(datasetID, currentID, currentCompoundClass, moleculeTitle, true)); + dispatch(appendCompoundColorOfDataset(datasetID, currentID, currentCompoundClass, moleculeTitle, false)); } }; diff --git a/js/components/datasets/redux/actions.js b/js/components/datasets/redux/actions.js index 6d70aeb90..a65611935 100644 --- a/js/components/datasets/redux/actions.js +++ b/js/components/datasets/redux/actions.js @@ -388,7 +388,8 @@ export const appendCompoundColorOfDataset = ( skipTracking = false ) => ({ type: constants.APPEND_COMPOUND_COLOR_OF_DATASET, - payload: { datasetID, compoundID, colorClass, compoundTitle, skipTracking: skipTracking } + payload: { datasetID, compoundID, colorClass, compoundTitle }, + skipTracking: skipTracking }); export const removeCompoundColorOfDataset = ( @@ -399,7 +400,18 @@ export const removeCompoundColorOfDataset = ( skipTracking = false ) => ({ type: constants.REMOVE_COMPOUND_COLOR_OF_DATASET, - payload: { datasetID, compoundID, colorClass, compoundTitle, skipTracking: skipTracking } + payload: { datasetID, compoundID, colorClass, compoundTitle }, + skipTracking: skipTracking +}); + +export const appendColorToAllCompoundsOfDataset = (datasetID, colorClass, cmpIds) => ({ + type: constants.APPEND_COLOR_TO_ALL_COMPOUNDS_OF_DATASET, + payload: { datasetID, colorClass, cmpIds } +}); + +export const removeColorFromAllCompoundsOfDataset = (datasetID, colorClass, cmpIds) => ({ + type: constants.REMOVE_COLOR_FROM_ALL_COMPOUNDS_OF_DATASET, + payload: { datasetID, colorClass, cmpIds } }); export const appendColorToSelectedColorFilter = colorClass => ({ diff --git a/js/components/datasets/redux/constants.js b/js/components/datasets/redux/constants.js index 60c8d09aa..7712b4675 100644 --- a/js/components/datasets/redux/constants.js +++ b/js/components/datasets/redux/constants.js @@ -74,6 +74,8 @@ export const constants = { APPEND_COMPOUND_COLOR_OF_DATASET: prefix + 'APPEND_COMPOUND_COLOR_OF_DATASET', REMOVE_COMPOUND_COLOR_OF_DATASET: prefix + 'REMOVE_COMPOUND_COLOR_OF_DATASET', + APPEND_COLOR_TO_ALL_COMPOUNDS_OF_DATASET: prefix + 'SET_COLOR_TO_ALL_COMPOUNDS_OF_DATASET', + REMOVE_COLOR_FROM_ALL_COMPOUNDS_OF_DATASET: prefix + 'REMOVE_COLOR_FROM_ALL_COMPOUNDS_OF_DATASET', APPEND_COLOR_TO_SELECTED_COLOR_FILTERS: prefix + 'APPEND_COLOR_TO_SELECTED_COLOR_FILTERS', REMOVE_COLOR_FROM_SELECTED_COLOR_FILTERS: prefix + 'REMOVE_COLOR_FROM_SELECTED_COLOR_FILTERS', diff --git a/js/components/projects/redux/dispatchActions.js b/js/components/projects/redux/dispatchActions.js index 72e43538a..83ae4a8ac 100644 --- a/js/components/projects/redux/dispatchActions.js +++ b/js/components/projects/redux/dispatchActions.js @@ -552,5 +552,5 @@ const getJobOverrides = async () => { const resultCall = await api({ url: `${base_url}/api/job_override/` }); - return resultCall.data.results[0].override; + return resultCall.data?.results[0]?.override; }; diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index ef5eb686b..c956c13a7 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -117,7 +117,9 @@ export const actionType = { COMPOUND_REMOVED_FROM_COLOR_GROUP: 'COMPOUND_REMOVED_FROM_COLOR_GROUP', TAG_DETAIL_VIEW: 'TAG_DETAIL_VIEW', SELECT_ALL_DATASET_COMPOUNDS: 'SELECT_ALL_DATASET_COMPOUNDS', - SELECTED_SELECT_ALL_BUTTON_FOR_DATASET: 'SELECTED_SELECT_ALL_BUTTON_FOR_DATASET' + SELECTED_SELECT_ALL_BUTTON_FOR_DATASET: 'SELECTED_SELECT_ALL_BUTTON_FOR_DATASET', + ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP: 'ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP', + ALL_COMPOUNDS_REMOVED_FROM_COLOR_GROUP: 'ALL_COMPOUNDS_REMOVED_FROM_COLOR_GROUP' }; export const snapshotSwitchManualActions = [ diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index 07b55b81f..f4e137808 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -168,6 +168,7 @@ import { } from '../../components/preview/tags/redux/dispatchActions'; import { turnSide } from '../../components/preview/viewerControls/redux/actions'; import { getQualityOffActions } from './utils'; +import { compoundsColors } from '../../components/preview/compounds/redux/constants'; export const addCurrentActionsListToSnapshot = (snapshot, project, nglViewList) => async (dispatch, getState) => { let projectID = project && project.projectID; @@ -437,6 +438,13 @@ const saveActionsList = (project, snapshot, actionList, nglViewList) => async (d currentActions ); + getCurrentActionListOfPaintAllMolecules( + orderedActionList, + actionType.ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP, + getCollectionOfDatasetColors(currentColorsOfCompounds), + currentActions + ); + getCurrentActionList( orderedActionList, actionType.COMPOUND_ADDED_TO_COLOR_GROUP, @@ -739,6 +747,27 @@ const getCurrentActionListOfSelectAllMolecules = (orderedActionList, type, colle } }; +const getCurrentActionListOfPaintAllMolecules = (orderedActionList, type, collection, currentActions) => { + const definedColors = Object.keys(compoundsColors); + definedColors.forEach(color => { + let action = orderedActionList.find(action => action.type === type && action.color === color); + if (action && collection) { + const datasetId = action.dataset_id; + let actionItems = action.items; + let items = []; + collection.forEach(data => { + if (data.datasetId === datasetId && data.color === color) { + let item = actionItems.find(ai => ai === data.id); + if (item) { + items.push(item); + } + } + }); + currentActions.push(Object.assign({ ...action, items: items })); + } + }); +}; + const getCurrentActionListOfShoppingCart = (orderedActionList, type, collection, currentActions) => { let actionList = orderedActionList.filter(action => action.type === type); @@ -889,7 +918,7 @@ export const restoreStateBySavedActionList = () => (dispatch, getState) => { const currentActionList = state.trackingReducers.current_actions_list; const orderedActionList = currentActionList.sort((a, b) => a.timestamp - b.timestamp); - let onCancel = () => { }; + let onCancel = () => {}; dispatch(loadTargetList(onCancel)) .then(() => { dispatch(restoreTargetActions(orderedActionList)); @@ -930,14 +959,14 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, await dispatch( loadMoleculeGroupsOfTarget({ isStateLoaded: false, - setOldUrl: url => { }, + setOldUrl: url => {}, target_on: targetId }) ) .catch(error => { throw error; }) - .finally(() => { }); + .finally(() => {}); await dispatch(restoreSitesActions(orderedActionList)); await dispatch(loadData(orderedActionList, targetId, majorView)); @@ -1483,16 +1512,16 @@ const restoreAllSelectionActions = (moleculesAction, stage, isSelection) => (dis let actions = isSelection === true ? moleculesAction.filter( - action => - action.type === actionType.ALL_TURNED_ON && - (action.object_type === actionObjectType.INSPIRATION || action.object_type === actionObjectType.MOLECULE) - ) + action => + action.type === actionType.ALL_TURNED_ON && + (action.object_type === actionObjectType.INSPIRATION || action.object_type === actionObjectType.MOLECULE) + ) : moleculesAction.filter( - action => - action.type === actionType.ALL_TURNED_ON && - (action.object_type === actionObjectType.CROSS_REFERENCE || - action.object_type === actionObjectType.COMPOUND) - ); + action => + action.type === actionType.ALL_TURNED_ON && + (action.object_type === actionObjectType.CROSS_REFERENCE || + action.object_type === actionObjectType.COMPOUND) + ); if (actions) { actions.forEach(action => { @@ -1545,21 +1574,36 @@ const restoreSelectAllMolecules = moleculesAction => (dispatch, getState) => { } }; +const restorePaintAllCompounds = compoundActions => (dispatch, getState) => { + let actions = compoundActions?.filter(ma => ma.type === actionType.ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP); + actions?.forEach(action => { + if (action?.items && action?.color && action?.dataset_id) { + const state = getState(); + action.items.forEach(ai => { + const cmp = getCompoundById(ai, action.dataset_id, state); + if (cmp) { + dispatch(appendCompoundColorOfDataset(action.dataset_id, cmp.id, action.color, cmp.name, true)); + } + }); + } + }); +}; + const restoreAllSelectionByTypeActions = (moleculesAction, stage, isSelection) => (dispatch, getState) => { const state = getState(); let actions = isSelection === true ? moleculesAction.filter( - action => - action.type === actionType.SELECTED_TURNED_ON_BY_TYPE && - (action.object_type === actionObjectType.INSPIRATION || action.object_type === actionObjectType.MOLECULE) - ) + action => + action.type === actionType.SELECTED_TURNED_ON_BY_TYPE && + (action.object_type === actionObjectType.INSPIRATION || action.object_type === actionObjectType.MOLECULE) + ) : moleculesAction.filter( - action => - action.type === actionType.SELECTED_TURNED_ON_BY_TYPE && - (action.object_type === actionObjectType.CROSS_REFERENCE || - action.object_type === actionObjectType.COMPOUND) - ); + action => + action.type === actionType.SELECTED_TURNED_ON_BY_TYPE && + (action.object_type === actionObjectType.CROSS_REFERENCE || + action.object_type === actionObjectType.COMPOUND) + ); if (actions) { actions.forEach(action => { @@ -1642,13 +1686,14 @@ export const restoreRepresentationActions = (moleculesActions, stages) => async } // if action points to non existing representation it was probably a default one so try to map it to current default one const fixUuid = (representationToCheck, objectId) => { - const representationExists = objectsInView[objectId].representations.some(representation => { - return representation.uuid === representationToCheck.uuid; - }) || addedRepresentations.includes(representationToCheck.uuid); + const representationExists = + objectsInView[objectId].representations.some(representation => { + return representation.uuid === representationToCheck.uuid; + }) || addedRepresentations.includes(representationToCheck.uuid); if (!representationExists && defaultStructuresMap.hasOwnProperty(objectId)) { representationToCheck.lastKnownID = defaultStructuresMap[objectId]; } - } + }; // here the object id is actually protein_code_object_type and is identifier in NGL view so it's ok to use object_id in here for (const action of moleculesActions) { @@ -1666,7 +1711,6 @@ export const restoreRepresentationActions = (moleculesActions, stages) => async await dispatch(changeMolecularRepresentationForSnapshotRestoration(action, nglView)); } } - }; export const restoreTabActions = moleculesAction => (dispatch, getState) => { @@ -1866,6 +1910,8 @@ export const restoreCompoundsActions = (orderedActionList, stage) => (dispatch, } }); + dispatch(restorePaintAllCompounds(compoundsAction)); + let compoundsColorGroupActions = compoundsAction?.filter( action => action.type === actionType.COMPOUND_ADDED_TO_COLOR_GROUP ); @@ -2051,6 +2097,19 @@ export const getCompoundByName = (name, datasetID, state) => { return molecule; }; +export const getCompoundById = (cmpId, datasetID, state) => { + let moleculeList = state.datasetsReducers.moleculeLists; + let molecule = null; + + if (moleculeList) { + let moleculeListOfDataset = moleculeList[datasetID]; + if (moleculeListOfDataset) { + molecule = moleculeListOfDataset.find(m => m.id === cmpId); + } + } + return molecule; +}; + export const undoAction = (stages = []) => (dispatch, getState) => { dispatch(setIsUndoRedoAction(true)); let action = dispatch(getUndoAction()); @@ -2275,6 +2334,12 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_REMOVED_FROM_COLOR_GROUP: dispatch(handleCompoundColorAction(action, true)); break; + case actionType.ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP: + dispatch(handlePaintAllCompoundsAction(action, false)); + break; + case actionType.ALL_COMPOUNDS_REMOVED_FROM_COLOR_GROUP: + dispatch(handlePaintAllCompoundsAction(action, true)); + break; case actionType.MOLECULE_SELECTED: dispatch(handleSelectMoleculeAction(action, false)); break; @@ -2544,6 +2609,12 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_REMOVED_FROM_COLOR_GROUP: dispatch(handleCompoundColorAction(action, false)); break; + case actionType.ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP: + dispatch(handlePaintAllCompoundsAction(action, true)); + break; + case actionType.ALL_COMPOUNDS_REMOVED_FROM_COLOR_GROUP: + dispatch(handlePaintAllCompoundsAction(action, false)); + break; case actionType.MOLECULE_SELECTED: dispatch(handleSelectMoleculeAction(action, true)); break; @@ -3008,6 +3079,22 @@ const handleCompoundColorAction = (action, isAdded) => (dispatch, getState) => { } }; +const handlePaintAllCompoundsAction = (action, arePainted) => (dispatch, getState) => { + const state = getState(); + if (action?.items) { + action.items.forEach(cmpId => { + const cmp = getCompoundById(cmpId, action?.dataset_id, state); + if (cmp) { + if (arePainted) { + dispatch(appendCompoundColorOfDataset(action?.dataset_id, cmpId, action?.color, cmp.name, true)); + } else { + dispatch(removeCompoundColorOfDataset(action?.dataset_id, cmpId, action?.color, cmp.name, true)); + } + } + }); + } +}; + const handleSelectMoleculeAction = (action, isSelected) => (dispatch, getState) => { if (action) { dispatch(handleSelectMoleculeByName(action.object_name)); @@ -3807,11 +3894,11 @@ export const updateTrackingActions = action => (dispatch, getState) => { method: METHOD.PUT, data: JSON.stringify(dataToSend) }) - .then(() => { }) + .then(() => {}) .catch(error => { throw new Error(error); }) - .finally(() => { }); + .finally(() => {}); } else { return Promise.resolve(); } @@ -3821,7 +3908,7 @@ export const updateTrackingActions = action => (dispatch, getState) => { }; function groupArrayOfObjects(list, key) { - return list.reduce(function (rv, x) { + return list.reduce(function(rv, x) { (rv[x[key]] = rv[x[key]] || []).push(x); return rv; }, {}); @@ -3846,11 +3933,11 @@ export const setAndUpdateTrackingActions = (actionList, projectID) => (dispatch, method: METHOD.PUT, data: JSON.stringify(dataToSend) }) - .then(() => { }) + .then(() => {}) .catch(error => { throw new Error(error); }) - .finally(() => { }); + .finally(() => {}); } else { return Promise.resolve(); } diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js index 76c3ce0fd..419857214 100644 --- a/js/reducers/tracking/trackingActions.js +++ b/js/reducers/tracking/trackingActions.js @@ -204,6 +204,34 @@ export const findTrackAction = (action, state) => (dispatch, getState) => { text: `${actionDescription.ALL} ${paylodTypeDescription} ${actionDescription.TURNED_OFF} ${objectType}` }; } + } else if (action.type === customDatasetConstants.APPEND_COLOR_TO_ALL_COMPOUNDS_OF_DATASET) { + let objectType = actionObjectType.COMPOUND; + + trackAction = { + type: actionType.ALL_COMPOUNDS_ADDED_TO_COLOR_GROUP, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: objectType, + items: action.payload.cmpIds, + color: action.payload.colorClass, + dataset_id: action.payload.datasetID, + text: `All compounds of dataset ${action.payload.datasetID} were colored with color ${action.payload.colorClass}` + }; + } else if (action.type === customDatasetConstants.REMOVE_COLOR_FROM_ALL_COMPOUNDS_OF_DATASET) { + let objectType = actionObjectType.COMPOUND; + + trackAction = { + type: actionType.ALL_COMPOUNDS_REMOVED_FROM_COLOR_GROUP, + annotation: actionAnnotation.CLEAR, + timestamp: Date.now(), + username: username, + object_type: objectType, + items: action.payload.cmpIds, + color: action.payload.colorClass, + dataset_id: action.payload.datasetID, + text: `All compounds of dataset ${action.payload.datasetID} were unpainted with color ${action.payload.colorClass}` + }; } else if (action.type === selectionConstants.SET_SELECT_ALL_MOLECULES) { if (action && action.items) { let objectType = actionObjectType.MOLECULE;