diff --git a/js/components/preview/viewerControls/displayControls/index.js b/js/components/preview/viewerControls/displayControls/index.js index a16125bd7..ea93e4e4e 100644 --- a/js/components/preview/viewerControls/displayControls/index.js +++ b/js/components/preview/viewerControls/displayControls/index.js @@ -10,6 +10,8 @@ import { addComponentRepresentation, removeComponentRepresentation, updateComponentRepresentation, + updateComponentRepresentationVisibility, + updateComponentRepresentationVisibilityAll, changeComponentRepresentation } from '../../../../reducers/ngl/actions'; import { deleteObject } from '../../../../reducers/ngl/dispatchActions'; @@ -52,7 +54,10 @@ export default memo(({ open, onClose }) => { const newVisibility = !r.getVisibility(); // update in redux representation.params.visible = newVisibility; - dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation)); + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); + dispatch( + updateComponentRepresentationVisibility(parentKey, representation.uuid, representation, newVisibility) + ); // update in nglView r.setVisibility(newVisibility); } @@ -144,10 +149,12 @@ export default memo(({ open, onClose }) => { // update in nglView r.setVisibility(newVisibility); // update in redux - dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation)); + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); } }); }); + + dispatch(updateComponentRepresentationVisibilityAll(parentKey, newVisibility)); }; const hasAllRepresentationVisibled = parentKey => { diff --git a/js/reducers/ngl/actions.js b/js/reducers/ngl/actions.js index e58f5e040..233dabb06 100644 --- a/js/reducers/ngl/actions.js +++ b/js/reducers/ngl/actions.js @@ -10,12 +10,41 @@ export const deleteNglObject = target => ({ target }); -export const updateComponentRepresentation = (objectInViewID, representationID, newRepresentation, change) => ({ +export const updateComponentRepresentationVisibility = ( + objectInViewID, + representationID, + representation, + newVisibility, + skipTracking = false +) => ({ + type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY, + representationID, + representation, + newVisibility, + objectInViewID, + skipTracking +}); + +export const updateComponentRepresentationVisibilityAll = (objectInViewID, newVisibility, skipTracking = false) => ({ + type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL, + newVisibility, + objectInViewID, + skipTracking +}); + +export const updateComponentRepresentation = ( + objectInViewID, + representationID, + newRepresentation, + change, + skipTracking = false +) => ({ type: CONSTANTS.UPDATE_COMPONENT_REPRESENTATION, representationID, newRepresentation, objectInViewID, - change + change, + skipTracking }); export const addComponentRepresentation = (objectInViewID, newRepresentation, skipTracking = false) => ({ @@ -51,7 +80,7 @@ export const setNglViewParams = (key, value, stage = undefined, objectId = undef }; }; -export const setBackgroundColor = (color) => { +export const setBackgroundColor = color => { return { type: CONSTANTS.SET_BACKGROUND_COLOR, payload: color diff --git a/js/reducers/ngl/constants.js b/js/reducers/ngl/constants.js index 4ef07ef26..f35064079 100644 --- a/js/reducers/ngl/constants.js +++ b/js/reducers/ngl/constants.js @@ -4,6 +4,8 @@ export const CONSTANTS = { LOAD_OBJECT: prefix + 'LOAD_OBJECT', DELETE_OBJECT: prefix + 'DELETE_OBJECT', // NGL Component Representation + UPDATE_COMPONENT_REPRESENTATION_VISIBILITY: prefix + 'UPDATE_COMPONENT_REPRESENTATION_VISIBILITY', + UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL: prefix + 'UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL', UPDATE_COMPONENT_REPRESENTATION: prefix + 'UPDATE_COMPONENT_REPRESENTATION', REMOVE_COMPONENT_REPRESENTATION: prefix + 'REMOVE_COMPONENT_REPRESENTATION', ADD_COMPONENT_REPRESENTATION: prefix + 'ADD_COMPONENT_REPRESENTATION', diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index af5fededf..7f2183f77 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -49,6 +49,8 @@ export const actionType = { COMPOUND_SELECTED: 'COMPOUND_SELECTED', COMPOUND_DESELECTED: 'COMPOUND_DESELECTED', REPRESENTATION_UPDATED: 'REPRESENTATION_UPDATED', + REPRESENTATION_VISIBILITY_UPDATED: 'REPRESENTATION_VISIBILITY_UPDATED', + REPRESENTATION_VISIBILITY_ALL_UPDATED: 'REPRESENTATION_VISIBILITY_ALL_UPDATED', REPRESENTATION_ADDED: 'REPRESENTATION_ADDED', REPRESENTATION_REMOVED: 'REPRESENTATION_REMOVED', REPRESENTATION_CHANGED: 'REPRESENTATION_CHANGED', @@ -76,11 +78,13 @@ export const actionDescription = { SELECTED: 'was selected', DESELECTED: 'was deselected', HIDDEN: 'hidden', + VISIBLE: 'visible', CANCELED: 'canceled', ADDED: 'was added', REMOVED: 'was removed', CHANGED: 'was changed', UPDATED: 'was updated', + VISIBILITY: 'visiblity', TO_SHOPPING_CART: 'to shopping cart', FROM_SHOPPING_CART: 'from shopping cart', LIGAND: 'Ligand', @@ -94,8 +98,7 @@ export const actionDescription = { TARGET: 'Target', ALL: 'All', LIGANDS: 'Ligands', - SIDECHAINS: 'Sidechains', - INTERACTIONS: 'Interactions' + SIDECHAINS: 'Sidechains' }; export const actionObjectType = { diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index 3349ebdc0..082308f87 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -63,6 +63,8 @@ import { removeComponentRepresentation, addComponentRepresentation, updateComponentRepresentation, + updateComponentRepresentationVisibility, + updateComponentRepresentationVisibilityAll, changeComponentRepresentation } from '../../../js/reducers/ngl/actions'; import * as listType from '../../constants/listTypes'; @@ -1318,6 +1320,12 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, true)); break; + case actionType.REPRESENTATION_VISIBILITY_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAction(action, false, majorView)); + break; + case actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAllAction(action, false, majorView)); + break; case actionType.REPRESENTATION_UPDATED: dispatch(handleUpdateRepresentationAction(action, false, majorView)); break; @@ -1455,6 +1463,12 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => { case actionType.COMPOUND_DESELECTED: dispatch(handleCompoundAction(action, false)); break; + case actionType.REPRESENTATION_VISIBILITY_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAction(action, true, majorView)); + break; + case actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED: + dispatch(handleUpdateRepresentationVisibilityAllAction(action, true, majorView)); + break; case actionType.REPRESENTATION_UPDATED: dispatch(handleUpdateRepresentationAction(action, true, majorView)); break; @@ -1825,6 +1839,56 @@ const removeRepresentation = (action, parentKey, representation, nglView, skipTr } }; +const handleUpdateRepresentationVisibilityAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + let parentKey = action.object_id; + let representation = action.representation; + + const comp = nglView.stage.getComponentsByName(parentKey).first; + comp.eachRepresentation(r => { + if (r.uuid === representation.uuid || r.uuid === representation.lastKnownID) { + const newVisibility = isAdd ? action.value : !action.value; + // update in redux + representation.params.visible = newVisibility; + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); + dispatch( + updateComponentRepresentationVisibility(parentKey, representation.uuid, representation, newVisibility) + ); + // update in nglView + r.setVisibility(newVisibility); + } + }); + } +}; + +const handleUpdateRepresentationVisibilityAllAction = (action, isAdd, nglView) => (dispatch, getState) => { + if (action) { + const state = getState(); + let parentKey = action.object_id; + let objectsInView = state.nglReducers.objectsInView; + let newVisibility = isAdd ? action.value : !action.value; + + const representations = (objectsInView[parentKey] && objectsInView[parentKey].representations) || []; + const comp = nglView.stage.getComponentsByName(parentKey).first; + + if (representations) { + representations.forEach((representation, index) => { + comp.eachRepresentation(r => { + if (r.uuid === representation.uuid || r.uuid === representation.lastKnownID) { + representation.params.visible = newVisibility; + // update in nglView + r.setVisibility(newVisibility); + // update in redux + dispatch(updateComponentRepresentation(parentKey, representation.uuid, representation, '', true)); + } + }); + }); + + dispatch(updateComponentRepresentationVisibilityAll(parentKey, newVisibility)); + } + } +}; + const handleUpdateRepresentationAction = (action, isAdd, nglView) => (dispatch, getState) => { if (action) { dispatch(updateRepresentation(isAdd, action.change, action.object_id, action.representation, nglView)); diff --git a/js/reducers/tracking/trackingActions.js b/js/reducers/tracking/trackingActions.js index f48d879cc..b4c3b2aff 100644 --- a/js/reducers/tracking/trackingActions.js +++ b/js/reducers/tracking/trackingActions.js @@ -770,8 +770,49 @@ export const findTrackAction = (action, state) => { text: `${actionDescription.SURFACE} ${actionDescription.TURNED_OFF} ${objectType} ${objectName} of dataset: ${action.payload.datasetID}` }; } + } else if (action.type === nglConstants.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY) { + let objectType = actionObjectType.REPRESENTATION; + let value = action.newVisibility; + let valueDescription = value === true ? actionDescription.VISIBLE : actionDescription.HIDDEN; + + trackAction = { + type: actionType.REPRESENTATION_VISIBILITY_UPDATED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.REPRESENTATION, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + representation_id: action.representationID, + representation: action.representation, + value: value, + text: `${objectType} '${action.representation?.type}' ${actionDescription.VISIBILITY} of ${action.objectInViewID} ${actionDescription.CHANGED} to: ${valueDescription}` + }; + } else if (action.type === nglConstants.UPDATE_COMPONENT_REPRESENTATION_VISIBILITY_ALL) { + let objectType = actionObjectType.REPRESENTATION; + let value = action.newVisibility; + let valueDescription = value === true ? actionDescription.VISIBLE : actionDescription.HIDDEN; + + trackAction = { + type: actionType.REPRESENTATION_VISIBILITY_ALL_UPDATED, + annotation: actionAnnotation.CHECK, + timestamp: Date.now(), + username: username, + object_type: actionObjectType.REPRESENTATION, + object_name: action.objectInViewID, + object_id: action.objectInViewID, + value: value, + text: `${objectType} ${actionDescription.VISIBILITY} of ${action.objectInViewID} ${actionDescription.CHANGED} to: ${valueDescription}` + }; } else if (action.type.includes(nglConstants.UPDATE_COMPONENT_REPRESENTATION)) { let objectType = actionObjectType.REPRESENTATION; + let key = action.change?.key; + let oldValue = action.change?.oldValue; + let newValue = action.change?.value; + let valueDescription = + key !== 'clipCenter' + ? `from value: ${oldValue} to value: ${newValue}` + : getClipCenterChange(oldValue, newValue); trackAction = { type: actionType.REPRESENTATION_UPDATED, @@ -784,7 +825,7 @@ export const findTrackAction = (action, state) => { representation_id: action.representationID, representation: action.newRepresentation, change: action.change, - text: `${objectType} '${action.change?.key}' of ${action.objectInViewID} ${actionDescription.UPDATED} from value: ${action.change?.oldValue} to value: ${action.change?.value}` + text: `${objectType} '${key}' of ${action.objectInViewID} ${actionDescription.UPDATED} ${valueDescription}` }; } else if (action.type.includes(nglConstants.ADD_COMPONENT_REPRESENTATION)) { let objectType = actionObjectType.REPRESENTATION; @@ -889,10 +930,17 @@ export const findTrackAction = (action, state) => { oldSetting: oldSetting, newSetting: newSetting, getText: function() { - return "Clip far of NGL " + actionDescription.CHANGED + " from value: " + this.oldSetting + " to value: " + this.newSetting; + return ( + 'Clip far of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); }, text: `Clip far of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` - }; + }; } else if (action.type.includes(nglConstants.SET_CLIP_DIST)) { let oldSetting = action.payload.oldValue; let newSetting = action.payload.newValue; @@ -908,10 +956,17 @@ export const findTrackAction = (action, state) => { oldSetting: oldSetting, newSetting: newSetting, getText: function() { - return "Clip dist of NGL " + actionDescription.CHANGED + " from value: " + this.oldSetting + " to value: " + this.newSetting; + return ( + 'Clip dist of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); }, text: `Clip dist of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` - }; + }; } else if (action.type.includes(nglConstants.SET_FOG_NEAR)) { let oldSetting = action.payload.oldValue; let newSetting = action.payload.newValue; @@ -927,10 +982,17 @@ export const findTrackAction = (action, state) => { oldSetting: oldSetting, newSetting: newSetting, getText: function() { - return "Fog near of NGL " + actionDescription.CHANGED + " from value: " + this.oldSetting + " to value: " + this.newSetting; + return ( + 'Fog near of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); }, text: `For near of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` - }; + }; } else if (action.type.includes(nglConstants.SET_FOG_FAR)) { let oldSetting = action.payload.oldValue; let newSetting = action.payload.newValue; @@ -946,10 +1008,17 @@ export const findTrackAction = (action, state) => { oldSetting: oldSetting, newSetting: newSetting, getText: function() { - return "Fog far of NGL " + actionDescription.CHANGED + " from value: " + this.oldSetting + " to value: " + this.newSetting; + return ( + 'Fog far of NGL ' + + actionDescription.CHANGED + + ' from value: ' + + this.oldSetting + + ' to value: ' + + this.newSetting + ); }, text: `For far of NGL ${actionDescription.CHANGED} from value: ${oldSetting} to value: ${newSetting}` - }; + }; } } return trackAction; @@ -1005,6 +1074,22 @@ const getTypeDescriptionOfSelectedAllAction = type => { } }; +const getClipCenterChange = (oldValue, newValue) => { + let description = ''; + if (oldValue && newValue) { + if (oldValue.x !== newValue.x) { + description += ' from value: x:' + oldValue.x + ' to value: x:' + newValue.x; + } + if (oldValue.y !== newValue.y) { + description += ' from value: y:' + oldValue.y + ' to value: y:' + newValue.y; + } + if (oldValue.z !== newValue.z) { + description += ' from value: z:' + oldValue.z + ' to value: z:' + newValue.z; + } + } + return description; +}; + export const createInitAction = target_on => (dispatch, getState) => { const state = getState(); const username = DJANGO_CONTEXT['username'];