diff --git a/js/components/datasets/datasetMoleculeList.js b/js/components/datasets/datasetMoleculeList.js index cd291ed0e..c0cb10c07 100644 --- a/js/components/datasets/datasetMoleculeList.js +++ b/js/components/datasets/datasetMoleculeList.js @@ -698,10 +698,10 @@ const DatasetMoleculeList = ({ title, datasetID, url }) => { showCrossReferenceModal previousItemData={index > 0 && array[index - 1]} nextItemData={index < array?.length && array[index + 1]} - L={ligandList.includes(data.id)} - P={proteinList.includes(data.id)} - C={complexList.includes(data.id)} - S={surfaceList.includes(data.id)} + L={ligandList?.includes(data.id)} + P={proteinList?.includes(data.id)} + C={complexList?.includes(data.id)} + S={surfaceList?.includes(data.id)} V={false} moveMolecule={moveMolecule} isCheckedToBuy={isCheckedToBuy} diff --git a/js/components/datasets/redux/dispatchActions.js b/js/components/datasets/redux/dispatchActions.js index 367162f7b..64475bc1a 100644 --- a/js/components/datasets/redux/dispatchActions.js +++ b/js/components/datasets/redux/dispatchActions.js @@ -94,7 +94,7 @@ export const addDatasetHitProtein = ( datasetID, skipTracking = false, representations = undefined -) => dispatch => { +) => async dispatch => { dispatch(appendProteinList(datasetID, generateMoleculeCompoundId(data), skipTracking)); return dispatch( loadObject({ @@ -132,7 +132,7 @@ export const addDatasetComplex = ( datasetID, skipTracking = false, representations = undefined -) => dispatch => { +) => async dispatch => { dispatch(appendComplexList(datasetID, generateMoleculeCompoundId(data), skipTracking)); return dispatch( loadObject({ @@ -160,7 +160,13 @@ export const removeDatasetComplex = (stage, data, colourToggle, datasetID, skipT dispatch(removeFromComplexList(datasetID, generateMoleculeCompoundId(data), skipTracking)); }; -export const addDatasetSurface = (stage, data, colourToggle, datasetID, representations = undefined) => dispatch => { +export const addDatasetSurface = ( + stage, + data, + colourToggle, + datasetID, + representations = undefined +) => async dispatch => { dispatch(appendSurfaceList(datasetID, generateMoleculeCompoundId(data))); return dispatch( loadObject({ @@ -195,8 +201,9 @@ export const addDatasetLigand = ( datasetID, skipTracking = false, representations = undefined -) => dispatch => { +) => async (dispatch, getState) => { dispatch(appendLigandList(datasetID, generateMoleculeCompoundId(data), skipTracking)); + console.count(`Grabbed orientation before loading dataset ligand`); const currentOrientation = stage.viewerControls.getOrientation(); return dispatch( loadObject({ @@ -206,13 +213,21 @@ export const addDatasetLigand = ( markAsRightSideLigand: true }) ).finally(() => { - const ligandOrientation = stage.viewerControls.getOrientation(); - dispatch(setOrientation(VIEWS.MAJOR_VIEW, ligandOrientation)); - - dispatch(appendMoleculeOrientation(getDatasetMoleculeID(datasetID, data?.id), ligandOrientation)); - - // keep current orientation of NGL View - stage.viewerControls.orient(currentOrientation); + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + if (!skipOrientation) { + const ligandOrientation = stage.viewerControls.getOrientation(); + dispatch(setOrientation(VIEWS.MAJOR_VIEW, ligandOrientation)); + + dispatch(appendMoleculeOrientation(getDatasetMoleculeID(datasetID, data?.id), ligandOrientation)); + + // keep current orientation of NGL View + if (!skipOrientation) { + console.count(`Before applying orientation after loading dataset ligand.`); + stage.viewerControls.orient(currentOrientation); + console.count(`After applying orientation after loading dataset ligand.`); + } + } }); }; diff --git a/js/components/datasets/redux/reducer.js b/js/components/datasets/redux/reducer.js index 864975191..1d1113a67 100644 --- a/js/components/datasets/redux/reducer.js +++ b/js/components/datasets/redux/reducer.js @@ -517,7 +517,11 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { allInspirations, filteredScoreProperties, selectedDatasetIndex, - tabValue + tabValue, + ligandLists, + proteinLists, + complexLists, + surfaceLists } = state; const newState = { @@ -530,12 +534,12 @@ export const datasetsReducers = (state = INITIAL_STATE, action = {}) => { filteredScoreProperties, selectedDatasetIndex, tabValue, - ligandLists: {}, - proteinLists: {}, - complexLists: {}, - surfaceLists: {}, inspirationLists: {}, - moleculeAllSelection: {} + moleculeAllSelection: {}, + ligandLists, + proteinLists, + complexLists, + surfaceLists }; Object.keys(moleculeLists).forEach(datasetID => initializeContainerLists(newState, datasetID)); diff --git a/js/components/nglView/constants/index.js b/js/components/nglView/constants/index.js index 1408c6259..e47f740ba 100644 --- a/js/components/nglView/constants/index.js +++ b/js/components/nglView/constants/index.js @@ -98,7 +98,7 @@ export const MOL_REPRESENTATION = { }; export const COMMON_PARAMS = { - warningIcon: true + warningIcon: 'warningIcon' }; export const MOL_REPRESENTATION_BUFFER = 'buffer'; diff --git a/js/components/nglView/renderingHelpers.js b/js/components/nglView/renderingHelpers.js index 34ae14188..8e0957dc7 100644 --- a/js/components/nglView/renderingHelpers.js +++ b/js/components/nglView/renderingHelpers.js @@ -32,7 +32,17 @@ const drawStripyBond = (atom_a, atom_b, color_a, color_b, label, size = 0.1, sha } }; -export function loadQualityFromFile(stage, file, quality, object_name, orientationMatrix, color, qualityType) { +export const loadQualityFromFile = async ( + stage, + file, + quality, + object_name, + orientationMatrix, + color, + qualityType, + skipOrientation +) => { + console.count(`loadQualityFromFile started`); // let goodids = // qualityType === QUALITY_TYPES.LIGAND // ? (quality && quality.goodids) || [] @@ -50,6 +60,7 @@ export function loadQualityFromFile(stage, file, quality, object_name, orientati let extension = qualityType === QUALITY_TYPES.LIGAND ? 'sdf' : 'pdb'; return stage.loadFile(file, { name: object_name, ext: extension }).then(function(comp) { + console.count(`loadQualityFromFile file loaded`); let representationStructures = []; let rgbColor = hexToRgb(color); @@ -196,19 +207,25 @@ export function loadQualityFromFile(stage, file, quality, object_name, orientati } } - if (orientationMatrix && orientationMatrix.elements) { - const matrix = new Matrix4(); - matrix.fromArray(orientationMatrix.elements); - - stage.viewerControls.orient(matrix); - } else if (orientationMatrix === undefined) { - comp.autoView('ligand'); + if (!skipOrientation) { + if (orientationMatrix && orientationMatrix.elements) { + const matrix = new Matrix4(); + matrix.fromArray(orientationMatrix.elements); + + console.count(`Before applying quality orientation matrix.`); + stage.viewerControls.orient(matrix); + console.count(`After applying quality orientation matrix.`); + } else if (orientationMatrix === undefined) { + comp.autoView('ligand'); + console.count(`Orientation matrix not found for quality, using autoView instead.`); + } } const reprArray = createRepresentationsArray(representationStructures); + console.count(`loadQualityFromFile finnished`); return assignRepresentationArrayToComp(reprArray, comp); }); -} +}; export const readQualityInformation = (name, text) => (dispatch, getState) => { const state = getState(); diff --git a/js/components/nglView/renderingObjects.js b/js/components/nglView/renderingObjects.js index 6c5907369..070da083a 100644 --- a/js/components/nglView/renderingObjects.js +++ b/js/components/nglView/renderingObjects.js @@ -27,7 +27,7 @@ const showSphere = ({ stage, input_dict, object_name, representations }) => { return Promise.resolve(assignRepresentationArrayToComp(reprArray, comp)); }; -const showLigand = ({ +const showLigand = async ({ stage, input_dict, object_name, @@ -35,9 +35,12 @@ const showLigand = ({ orientationMatrix, markAsRightSideLigand, loadQuality, - quality + quality, + state }) => { let stringBlob = new Blob([input_dict.sdf_info], { type: 'text/plain' }); + console.count(`showLigand started`); + const skipOrientation = state.trackingReducers.skipOrientationChange; if (loadQuality && quality && quality.badids?.length > 0) { return loadQualityFromFile( @@ -47,7 +50,8 @@ const showLigand = ({ object_name, orientationMatrix, input_dict.colour, - QUALITY_TYPES.LIGAND + QUALITY_TYPES.LIGAND, + skipOrientation ); } else { return loadLigandFromFile( @@ -57,7 +61,8 @@ const showLigand = ({ representations, markAsRightSideLigand, input_dict, - orientationMatrix + orientationMatrix, + skipOrientation ); } }; @@ -69,9 +74,12 @@ const loadLigandFromFile = ( representations, markAsRightSideLigand, input_dict, - orientationMatrix + orientationMatrix, + skipOrientation ) => { + console.count(`loadLigandFromFile started`); return stage.loadFile(stringBlob, { name: object_name, ext: 'sdf' }).then(comp => { + console.count(`loadLigandFromFile file loaded`); const reprArray = representations || createRepresentationsArray([ @@ -86,14 +94,19 @@ const loadLigandFromFile = ( ) ]); - if (orientationMatrix && orientationMatrix.elements) { - const matrix = new Matrix4(); - matrix.fromArray(orientationMatrix.elements); - - stage.viewerControls.orient(matrix); - } else if (orientationMatrix === undefined) { - comp.autoView('ligand'); + if (!skipOrientation) { + if (orientationMatrix && orientationMatrix.elements) { + const matrix = new Matrix4(); + matrix.fromArray(orientationMatrix.elements); + console.count(`Before applying orientation matrix - loadLigandFromFile`); + stage.viewerControls.orient(matrix); + console.count(`After applying orientation matrix - loadLigandFromFile`); + } else if (orientationMatrix === undefined) { + comp.autoView('ligand'); + console.count(`Orientation matrix not found for loadLigandFromFile, using autoView instead.`); + } } + console.count(`loadLigandFromFile finished`); return assignRepresentationArrayToComp(reprArray, comp); }); }; @@ -204,7 +217,7 @@ const renderComplex = (ol, representations, orientationMatrix) => { // stage.viewerControls.orient(orientationMatrix); // } else if (orientationMatrix === undefined) { // comp.autoView('ligand'); - // //TODO setFocus should be in condition + // //TODO: setFocus should be in condition // comp.stage.setFocus(focus_let_temp); // } @@ -360,15 +373,22 @@ const showArrow = ({ stage, input_dict, object_name, representations, orientatio return Promise.resolve(assignRepresentationArrayToComp(reprArray, comp)); }; -const showProtein = ({ stage, input_dict, object_name, representations, orientationMatrix }) => +const showProtein = ({ stage, input_dict, object_name, representations, orientationMatrix, state }) => stage.loadFile(input_dict.prot_url, { name: object_name, ext: 'pdb', defaultAssembly: 'BU1' }).then(comp => { const reprArray = representations || createRepresentationsArray([createRepresentationStructure(input_dict.nglProtStyle, {})]); - if (orientationMatrix) { - stage.viewerControls.orient(orientationMatrix); - } else if (orientationMatrix === undefined) { - comp.autoView(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + + if (!skipOrientation) { + if (orientationMatrix) { + console.count(`Before applying orientation matrix - showProtein`); + stage.viewerControls.orient(orientationMatrix); + console.count(`After applying orientation matrix - showProtein`); + } else if (orientationMatrix === undefined) { + comp.autoView(); + console.count(`Orientation matrix not found for showProtein, using autoView instead.`); + } } return Promise.resolve(assignRepresentationArrayToComp(reprArray, comp)); }); @@ -431,7 +451,7 @@ const showHotspot = ({ stage, input_dict, object_name, representations }) => { } }; -const showDensity = ({ stage, input_dict, object_name, representations, dispatch }) => { +const showDensity = ({ stage, input_dict, object_name, representations }) => { let densityParams_event = { color: input_dict.color_DENSITY, isolevel: input_dict.isolevel_DENSITY, diff --git a/js/components/preview/molecule/moleculeView/moleculeView.js b/js/components/preview/molecule/moleculeView/moleculeView.js index 6d5eae6cb..b76c83fb2 100644 --- a/js/components/preview/molecule/moleculeView/moleculeView.js +++ b/js/components/preview/molecule/moleculeView/moleculeView.js @@ -270,12 +270,10 @@ const MoleculeView = memo( const ref = useRef(null); const currentID = (data && data.id) || undefined; const classes = useStyles(); - const key = 'mol_image'; const dispatch = useDispatch(); const target_on_name = useSelector(state => state.apiReducers.target_on_name); const filter = useSelector(state => state.selectionReducers.filter); - const url = new URL(base_url + '/api/molimg/' + data.id + '/'); const [img_data, setImg_data] = useState(img_data_init); const viewParams = useSelector(state => state.nglReducers.viewParams); @@ -301,22 +299,12 @@ const MoleculeView = memo( const hasAllValuesOn = isLigandOn && isProteinOn && isComplexOn; const hasSomeValuesOn = !hasAllValuesOn && (isLigandOn || isProteinOn || isComplexOn); - const areArrowsVisible = isLigandOn || isProteinOn || isComplexOn || isSurfaceOn || isDensityOn || isVectorOn; - let warningIconVisible = viewParams[COMMON_PARAMS.warningIcon] === true && hasAdditionalInformation === true; let isWireframeStyle = viewParams[NGL_PARAMS.contour_DENSITY]; const disableMoleculeNglControlButtons = useSelector(state => state.previewReducers.molecule.disableNglControlButtons[currentID]) || {}; - let tagEditIconVisible = true; - - const oldUrl = useRef(''); - const setOldUrl = url => { - oldUrl.current = url; - }; - const refOnCancel = useRef(); - const colourToggle = getRandomColor(data); const getCalculatedProps = useCallback( @@ -337,7 +325,6 @@ const MoleculeView = memo( ); const [densityModalOpen, setDensityModalOpen] = useState(false); - const [tagAddModalOpen, setTagAddModalOpen] = useState(false); const [moleculeTooltipOpen, setMoleculeTooltipOpen] = useState(false); const [tagEditorTooltipOpen, setTagEditorTooltipOpen] = useState(false); const moleculeImgRef = useRef(null); @@ -479,9 +466,6 @@ const MoleculeView = memo( }; const addNewComplex = (skipTracking = false) => { - // if (selectMoleculeSite) { - // selectMoleculeSite(data.site); - // } dispatch( withDisabledMoleculeNglControlButton(currentID, 'complex', async () => { await dispatch(addComplex(stage, data, colourToggle, skipTracking)); @@ -541,10 +525,6 @@ const MoleculeView = memo( }; const addNewDensity = async () => { - // if (selectMoleculeSite) { - // selectMoleculeSite(data.site); - // } - // Selecting quality will render ligand dispatch( withDisabledMoleculeNglControlButton(currentID, 'ligand', async () => { await dispatch( @@ -658,48 +638,6 @@ const MoleculeView = memo( }); }; - const handleClickOnDownArrow = async () => { - const refNext = ref.current.nextSibling; - scrollToElement(refNext); - - let dataValue = { - isLigandOn: isLigandOn, - isProteinOn: isProteinOn, - isComplexOn: isComplexOn, - isSurfaceOn: isSurfaceOn, - isQualityOn: isQualityOn, - isDensityOn: isDensityOn, - isDensityCustomOn: isDensityCustomOn, - isVectorOn: isVectorOn, - // objectsInView moved to moveMoleculeUpDown due to performance - colourToggle: colourToggle - }; - // Needs to be awaited since adding elements to NGL viewer is done asynchronously - await dispatch(moveMoleculeUpDown(stage, data, nextItemData, dataValue, ARROW_TYPE.DOWN)); - removeSelectedTypes([nextItemData], true); - }; - - const handleClickOnUpArrow = async () => { - const refPrevious = ref.current.previousSibling; - scrollToElement(refPrevious); - - let dataValue = { - isLigandOn: isLigandOn, - isProteinOn: isProteinOn, - isComplexOn: isComplexOn, - isSurfaceOn: isSurfaceOn, - isQualityOn: isQualityOn, - isDensityOn: isDensityOn, - isDensityCustomOn: isDensityCustomOn, - isVectorOn: isVectorOn, - // objectsInView moved to moveMoleculeUpDown due to performance - colourToggle: colourToggle - }; - // Needs to be awaited since adding elements to NGL viewer is done asynchronously - await dispatch(moveMoleculeUpDown(stage, data, previousItemData, dataValue, ARROW_TYPE.UP)); - removeSelectedTypes([previousItemData], true); - }; - let moleculeTitle = data?.protein_code.replace(new RegExp(`${target_on_name}-`, 'i'), ''); const moleculeLPCControlButtonDisabled = ['ligand', 'protein', 'complex'].some( diff --git a/js/components/preview/molecule/redux/dispatchActions.js b/js/components/preview/molecule/redux/dispatchActions.js index 56db61e47..8a193e4d4 100644 --- a/js/components/preview/molecule/redux/dispatchActions.js +++ b/js/components/preview/molecule/redux/dispatchActions.js @@ -208,7 +208,7 @@ export const addComplex = ( colourToggle, skipTracking = false, representations = undefined -) => dispatch => { +) => async dispatch => { dispatch(appendComplexList(generateMoleculeId(data), skipTracking)); return dispatch( loadObject({ @@ -239,7 +239,7 @@ export const addSurface = ( colourToggle, skipTracking = false, representations = undefined -) => dispatch => { +) => async dispatch => { dispatch(appendSurfaceList(generateMoleculeId(data), skipTracking)); return dispatch( loadObject({ @@ -299,57 +299,59 @@ export const addDensity = ( }); }; -const setDensity = (stage, data, colourToggle, isWireframeStyle, skipTracking = false, representations = undefined) => ( - dispatch, - getState -) => { +const setDensity = ( + stage, + data, + colourToggle, + isWireframeStyle, + skipTracking = false, + representations = undefined +) => async (dispatch, getState) => { const prepParams = dispatch(getDensityChangedParams(isWireframeStyle)); const densityObject = generateDensityObject(data, colourToggle, base_url, isWireframeStyle); const combinedObject = { ...prepParams, ...densityObject }; - return Promise.resolve( - dispatch( - loadObject({ - target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, combinedObject), - stage, - previousRepresentations: representations, - orientationMatrix: null - }) - ).finally(() => { - const currentOrientation = stage.viewerControls.getOrientation(); - dispatch(setOrientation(VIEWS.MAJOR_VIEW, currentOrientation)); - let molDataId = generateMoleculeId(data); - if (!data.proteinData) { - dispatch(getProteinData(data)).then(i => { - if (i && i.length > 0) { - const proteinData = i[0]; - data.proteinData = proteinData; - - molDataId['render_event'] = data.proteinData.render_event; - molDataId['render_sigmaa'] = data.proteinData.render_sigmaa; - molDataId['render_diff'] = data.proteinData.render_diff; - molDataId['render_quality'] = data.proteinData.render_quality; - - dispatch(appendDensityList(generateMoleculeId(data), skipTracking)); - dispatch(appendToDensityListType(molDataId, skipTracking)); - if (data.proteinData.render_quality) { - return dispatch(addQuality(stage, data, colourToggle, true)); - } + dispatch( + loadObject({ + target: Object.assign({ display_div: VIEWS.MAJOR_VIEW }, combinedObject), + stage, + previousRepresentations: representations, + orientationMatrix: null + }) + ).finally(() => { + const currentOrientation = stage.viewerControls.getOrientation(); + dispatch(setOrientation(VIEWS.MAJOR_VIEW, currentOrientation)); + let molDataId = generateMoleculeId(data); + if (!data.proteinData) { + dispatch(getProteinData(data)).then(i => { + if (i && i.length > 0) { + const proteinData = i[0]; + data.proteinData = proteinData; + + molDataId['render_event'] = data.proteinData.render_event; + molDataId['render_sigmaa'] = data.proteinData.render_sigmaa; + molDataId['render_diff'] = data.proteinData.render_diff; + molDataId['render_quality'] = data.proteinData.render_quality; + + dispatch(appendDensityList(generateMoleculeId(data), skipTracking)); + dispatch(appendToDensityListType(molDataId, skipTracking)); + if (data.proteinData.render_quality) { + return dispatch(addQuality(stage, data, colourToggle, true)); } - }); - } else { - molDataId['render_event'] = data.proteinData.render_event; - molDataId['render_sigmaa'] = data.proteinData.render_sigmaa; - molDataId['render_diff'] = data.proteinData.render_diff; - molDataId['render_quality'] = data.proteinData.render_quality; - - dispatch(appendDensityList(generateMoleculeId(data), skipTracking)); - dispatch(appendToDensityListType(molDataId, skipTracking)); - if (data.proteinData.render_quality) { - return dispatch(addQuality(stage, data, colourToggle, true)); } + }); + } else { + molDataId['render_event'] = data.proteinData.render_event; + molDataId['render_sigmaa'] = data.proteinData.render_sigmaa; + molDataId['render_diff'] = data.proteinData.render_diff; + molDataId['render_quality'] = data.proteinData.render_quality; + + dispatch(appendDensityList(generateMoleculeId(data), skipTracking)); + dispatch(appendToDensityListType(molDataId, skipTracking)); + if (data.proteinData.render_quality) { + return dispatch(addQuality(stage, data, colourToggle, true)); } - }) - ); + } + }); }; const getDensityChangedParams = (isWireframeStyle = undefined) => (dispatch, getState) => { @@ -429,7 +431,7 @@ const setDensityCustom = ( isWireframeStyle, skipTracking = false, representations = undefined -) => (dispatch, getState) => { +) => async (dispatch, getState) => { let densityObject = dispatch(getDensityChangedParams()); densityObject = dispatch(toggleDensityWireframe(isWireframeStyle, densityObject)); const oldDensityData = dispatch(deleteDensityObject(data, colourToggle, stage, !isWireframeStyle)); @@ -492,7 +494,7 @@ export const addHitProtein = ( withQuality = false, skipTracking = false, representations = undefined -) => dispatch => { +) => async dispatch => { // data.sdf_info = molFile; dispatch(appendProteinList(generateMoleculeId(data), skipTracking)); let hitProteinObject = generateHitProteinObject(data, colourToggle, base_url); @@ -540,8 +542,9 @@ export const addLigand = ( withQuality = false, skipTracking = false, representations = undefined -) => (dispatch, getState) => { +) => async (dispatch, getState) => { // data.sdf_info = molFile; + console.count(`Grabbing orientation before loading ligand.`); const currentOrientation = stage.viewerControls.getOrientation(); dispatch(appendFragmentDisplayList(generateMoleculeId(data), skipTracking)); @@ -562,14 +565,20 @@ export const addLigand = ( loadQuality: hasAdditionalInformation, quality: qualityInformation }) - ).finally(() => { - const ligandOrientation = stage.viewerControls.getOrientation(); - dispatch(setOrientation(VIEWS.MAJOR_VIEW, ligandOrientation)); - - dispatch(appendMoleculeOrientation(data?.id, ligandOrientation)); - if (centerOn === false) { - // keep current orientation of NGL View - stage.viewerControls.orient(currentOrientation); + ).then(() => { + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + if (!skipOrientation) { + const ligandOrientation = stage.viewerControls.getOrientation(); + dispatch(setOrientation(VIEWS.MAJOR_VIEW, ligandOrientation)); + + dispatch(appendMoleculeOrientation(data?.id, ligandOrientation)); + if (centerOn === false) { + // keep current orientation of NGL View + console.count(`Before applying orientation matrix after loading ligand.`); + stage.viewerControls.orient(currentOrientation); + console.count(`After applying orientation matrix after loading ligand.`); + } } }); }; diff --git a/js/components/preview/projectHistoryPanel/ProjectHistory.js b/js/components/preview/projectHistoryPanel/ProjectHistory.js index 7a6d5e30a..556e8fbb2 100644 --- a/js/components/preview/projectHistoryPanel/ProjectHistory.js +++ b/js/components/preview/projectHistoryPanel/ProjectHistory.js @@ -1,4 +1,4 @@ -import React, { memo, useRef, useState } from 'react'; +import React, { memo, useRef, useState, useContext, useEffect, useCallback } from 'react'; import { Panel } from '../../common/Surfaces/Panel'; import { templateExtend, TemplateName, Orientation, Gitgraph } from '@gitgraph/react'; import { DynamicFeed, MergeType, PlayArrow, Refresh } from '@material-ui/icons'; @@ -14,6 +14,10 @@ import JobLauncherDialog from './JobLauncherDialog'; import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; import { SQUONK_NOT_AVAILABLE } from './constants'; import { PROJECTS_JOBS_PANEL_HEIGHT } from '../constants'; +import { changeSnapshot } from '../../../reducers/tracking/dispatchActionsSwitchSnapshot'; +import { NglContext } from '../../nglView/nglProvider'; +import { VIEWS } from '../../../constants/constants'; +import { useRouteMatch } from 'react-router-dom'; export const heightOfProjectHistory = PROJECTS_JOBS_PANEL_HEIGHT; @@ -73,6 +77,10 @@ const options = { export const ProjectHistory = memo(({ showFullHistory, graphKey, expanded, onExpanded, onTabChange }) => { const classes = useStyles(); + const { nglViewList, getNglView } = useContext(NglContext); + const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage; + let match = useRouteMatch(); + const paramsProjectID = match && match.params && match.params.projectId; const ref = useRef(null); const dispatch = useDispatch(); const currentSnapshotID = useSelector(state => state.projectReducers.currentSnapshot.id); @@ -80,6 +88,13 @@ export const ProjectHistory = memo(({ showFullHistory, graphKey, expanded, onExp const currentSnapshotJobList = useSelector(state => state.projectReducers.currentSnapshotJobList); const currentSnapshotTree = useSelector(state => state.projectReducers.currentSnapshotTree); const jobPopUpAnchorEl = useSelector(state => state.projectReducers.jobPopUpAnchorEl); + const isSnapshotDirty = useSelector(state => state.trackingReducers.isSnapshotDirty); + const currentProject = useSelector(state => state.projectReducers.currentProject); + const currentProjectID = currentProject && currentProject.projectID; + const projectID = paramsProjectID && paramsProjectID != null ? paramsProjectID : currentProjectID; + + const [tryToOpen, setTryToOpen] = useState(false); + const [transitionToSnapshot, setTransitionToSnapshot] = useState(null); const [jobPopupInfo, setJobPopupInfo] = useState({ hash: null, @@ -90,19 +105,35 @@ export const ProjectHistory = memo(({ showFullHistory, graphKey, expanded, onExp dispatch(setJobConfigurationDialogOpen(true)); }; - const handleClickOnCommit = commit => { - dispatch(setSelectedSnapshotToSwitch(commit.hash)); - dispatch(setIsOpenModalBeforeExit(true)); - }; + const handleClickOnCommit = useCallback(commit => { + setTryToOpen(true); + setTransitionToSnapshot(commit); + }, []); - const commitFunction = ({ title, hash, isSelected = false }) => ({ - hash: `${hash}`, - subject: `${title}`, - onMessageClick: handleClickOnCommit, - onClick: handleClickOnCommit, - style: - (isSelected === true && { dot: { size: 10, color: 'red', strokeColor: 'blue', strokeWidth: 2 } }) || undefined - }); + useEffect(() => { + if (isSnapshotDirty && tryToOpen && transitionToSnapshot) { + dispatch(setSelectedSnapshotToSwitch(transitionToSnapshot.hash)); + dispatch(setIsOpenModalBeforeExit(true)); + setTryToOpen(false); + } else if (!isSnapshotDirty && tryToOpen && transitionToSnapshot) { + dispatch(changeSnapshot(projectID, transitionToSnapshot.hash, nglViewList, stage)); + setTryToOpen(false); + } + }, [dispatch, isSnapshotDirty, nglViewList, projectID, stage, transitionToSnapshot, tryToOpen]); + + const commitFunction = useCallback( + ({ title, hash, isSelected = false }) => { + return { + hash: `${hash}`, + subject: `${title}`, + onMessageClick: handleClickOnCommit, + onClick: handleClickOnCommit, + style: + (isSelected === true && { dot: { size: 10, color: 'red', strokeColor: 'blue', strokeWidth: 2 } }) || undefined + }; + }, + [handleClickOnCommit] + ); const handleClickTriangle = (event, hash, jobInfo) => { dispatch(setJobPopUpAnchorEl(event.currentTarget)); diff --git a/js/components/projects/addProjectDetail/index.js b/js/components/projects/addProjectDetail/index.js index ced1d0277..d7db47272 100644 --- a/js/components/projects/addProjectDetail/index.js +++ b/js/components/projects/addProjectDetail/index.js @@ -51,13 +51,11 @@ export const AddProjectDetail = memo(({ handleCloseModal }) => { const validateProjectName = async value => { let error; - // console.log(`Project title validating and value is: ${value}`); if (!value) { error = 'Required!'; } else if (createDiscourse) { const response = await getExistingPost(value); - // console.log(response); if (response.data['Post url']) { error = 'Already exists!'; } diff --git a/js/components/projects/projectModal/index.js b/js/components/projects/projectModal/index.js index 8fa24dbee..c45be1bdb 100644 --- a/js/components/projects/projectModal/index.js +++ b/js/components/projects/projectModal/index.js @@ -81,13 +81,11 @@ export const ProjectModal = memo(({}) => { const validateProjectName = async value => { let error; - // console.log(`Project title validating and value is: ${value}`); if (!value) { error = 'Required!'; } else if (createDiscourse) { const response = await getExistingPost(value); - // console.log(response); if (response.data['Post url']) { error = 'Already exists!'; } diff --git a/js/components/projects/projectPreview/index.js b/js/components/projects/projectPreview/index.js index 3769024e0..38bc45afc 100644 --- a/js/components/projects/projectPreview/index.js +++ b/js/components/projects/projectPreview/index.js @@ -6,6 +6,7 @@ import { loadCurrentSnapshotByID, loadSnapshotByProjectID } from '../redux/dispa import { HeaderContext } from '../../header/headerContext'; import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; import { restoreCurrentActionsList } from '../../../reducers/tracking/dispatchActions'; +import { setIsSnapshotDirty } from '../../../reducers/tracking/actions'; import { setDownloadStructuresDialogOpen } from '../../snapshot/redux/actions'; export const ProjectPreview = memo(({}) => { @@ -28,6 +29,7 @@ export const ProjectPreview = memo(({}) => { .then(response => { if (response !== false) { isSnapshotLoaded.current = response; + dispatch(setIsSnapshotDirty(false)); setCanShow(true); } }) @@ -43,6 +45,7 @@ export const ProjectPreview = memo(({}) => { if (response) { if (response.session_project && `${response.session_project.id}` === projectId) { isSnapshotLoaded.current = response.id; + dispatch(setIsSnapshotDirty(false)); setCanShow(true); } else { setCanShow(false); diff --git a/js/components/projects/redux/dispatchActions.js b/js/components/projects/redux/dispatchActions.js index e8f77c3bf..c9fdff44e 100644 --- a/js/components/projects/redux/dispatchActions.js +++ b/js/components/projects/redux/dispatchActions.js @@ -22,7 +22,7 @@ import { createInitSnapshotFromCopy, getListOfSnapshots } from '../../snapshot/r import { SnapshotType } from './constants'; import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; import { sendInitTrackingActionByProjectId } from '../../../reducers/tracking/dispatchActions'; -import { resetTrackingState } from '../../../reducers/tracking/actions'; +import { resetTrackingState, setIsSnapshotDirty } from '../../../reducers/tracking/actions'; import { createProjectPost } from '../../../utils/discourse'; import { setOpenDiscourseErrorModal } from '../../../reducers/api/actions'; @@ -262,6 +262,7 @@ export const loadSnapshotTree = projectID => (dispatch, getState) => { }) .finally(() => { dispatch(setIsLoadingTree(false)); + dispatch(setIsSnapshotDirty(false)); }); }; diff --git a/js/components/snapshot/modals/saveSnapshotBeforeExit.js b/js/components/snapshot/modals/saveSnapshotBeforeExit.js index 16088baa9..a46a58fbc 100644 --- a/js/components/snapshot/modals/saveSnapshotBeforeExit.js +++ b/js/components/snapshot/modals/saveSnapshotBeforeExit.js @@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { NglContext } from '../../nglView/nglProvider'; import { useRouteMatch } from 'react-router-dom'; import { setIsOpenModalBeforeExit, setOpenSnapshotSavingDialog, setSelectedSnapshotToSwitch } from '../redux/actions'; -import { changeSnapshot } from '../../../reducers/tracking/dispatchActions'; +import { changeSnapshot } from '../../../reducers/tracking/dispatchActionsSwitchSnapshot'; import { VIEWS } from '../../../constants/constants'; export const SaveSnapshotBeforeExit = memo(() => { diff --git a/js/components/snapshot/redux/dispatchActions.js b/js/components/snapshot/redux/dispatchActions.js index ea0146da2..0ad28d8e9 100644 --- a/js/components/snapshot/redux/dispatchActions.js +++ b/js/components/snapshot/redux/dispatchActions.js @@ -36,9 +36,9 @@ import { saveCurrentActionsList, addCurrentActionsListToSnapshot, sendTrackingActionsByProjectId, - manageSendTrackingActions, - changeSnapshot + manageSendTrackingActions } from '../../../reducers/tracking/dispatchActions'; +import { changeSnapshot } from '../../../reducers/tracking/dispatchActionsSwitchSnapshot'; import { captureScreenOfSnapshot } from '../../userFeedback/browserApi'; import { setCurrentProject } from '../../projects/redux/actions'; import { createProjectPost } from '../../../utils/discourse'; @@ -207,7 +207,7 @@ const getAdditionalInfo = state => { const currentSnapshotVisibleDatasetsCompounds = Object.fromEntries( Object.entries(moleculeLists).map(([datasetID, mols]) => [ datasetID, - mols.filter(mol => ligandLists[datasetID].includes(mol.id)).map(mol => mol.name) + mols.filter(mol => ligandLists[datasetID]?.includes(mol.id)).map(mol => mol.name) ]) ); const currentSnapshotSelectedDatasetsCompounds = Object.fromEntries( diff --git a/js/reducers/ngl/actions.js b/js/reducers/ngl/actions.js index acb4de8f2..7c58ae185 100644 --- a/js/reducers/ngl/actions.js +++ b/js/reducers/ngl/actions.js @@ -192,13 +192,14 @@ export const setColorAction = (mapType, newValue, oldValue) => { }; }; -export const setWarningIconAction = (newValue, oldValue) => { +export const setWarningIconAction = (newValue, oldValue, skipTracking = false) => { return { type: CONSTANTS.SET_WARNING_ICON, payload: { newValue: newValue, oldValue: oldValue - } + }, + skipTracking: skipTracking }; }; diff --git a/js/reducers/ngl/dispatchActions.js b/js/reducers/ngl/dispatchActions.js index 802bb5fec..c36420e90 100644 --- a/js/reducers/ngl/dispatchActions.js +++ b/js/reducers/ngl/dispatchActions.js @@ -48,8 +48,9 @@ export const loadObject = ({ markAsRightSideLigand, loadQuality, quality -}) => dispatch => { +}) => async (dispatch, getState) => { if (stage) { + const state = getState(); dispatch(incrementCountOfPendingNglObjects(target.display_div)); const versionFixedTarget = JSON.parse(JSON.stringify(target)); @@ -57,6 +58,7 @@ export const loadObject = ({ versionFixedTarget.OBJECT_TYPE = OBJECT_TYPE.HIT_PROTEIN; } + console.count(`Before object is loaded`); return nglObjectDictionary[versionFixedTarget.OBJECT_TYPE]({ stage, input_dict: versionFixedTarget, @@ -66,9 +68,11 @@ export const loadObject = ({ markAsRightSideLigand, loadQuality, quality, - dispatch + dispatch, + state }) .then(representations => { + console.count(`Object loaded`); if (representations && representations.length > 0) { if (versionFixedTarget.OBJECT_TYPE === OBJECT_TYPE.DENSITY) { representations.forEach(repr => { @@ -183,8 +187,13 @@ export const setOrientationByInteraction = (div_id, orientation) => (dispatch, g export const centerOnLigandByMoleculeID = (stage, moleculeID) => (dispatch, getState) => { if (moleculeID && stage) { const state = getState(); - const storedOrientation = state.nglReducers.moleculeOrientations[moleculeID]; - stage.viewerControls.orient(storedOrientation); + const skipOrientation = state.trackingReducers.skipOrientationChange; + if (!skipOrientation) { + const storedOrientation = state.nglReducers.moleculeOrientations[moleculeID]; + console.count(`Before applying orientation centerOnLigandByMoleculeID`); + stage.viewerControls.orient(storedOrientation); + console.count(`After applying orientation centerOnLigandByMoleculeID`); + } } }; @@ -224,15 +233,22 @@ export const reloadNglViewFromSnapshot = (stage, display_div, snapshot) => (disp dispatch(setNglViewParams(param, snapshot.viewParams[param], stage, VIEWS.MAJOR_VIEW)); }); + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; // nglOrientations - const newOrientation = snapshot.nglOrientations[display_div]; - if (newOrientation) { - stage.viewerControls.orient(newOrientation.elements); - } + if (!skipOrientation) { + const newOrientation = snapshot.nglOrientations[display_div]; + console.count(`Orientation retrieved reloadNglViewFromSnapshot`); + if (newOrientation) { + console.count(`Before applying orientation reloadNglViewFromSnapshot`); + stage.viewerControls.orient(newOrientation.elements); + console.count(`After applying orientation reloadNglViewFromSnapshot`); + } - // set molecule orientations - if (snapshot.moleculeOrientations) { - dispatch(setMoleculeOrientations(snapshot.moleculeOrientations)); + // set molecule orientations + if (snapshot.moleculeOrientations) { + dispatch(setMoleculeOrientations(snapshot.moleculeOrientations)); + } } } }); @@ -298,9 +314,9 @@ export const setElectronDesityMapColor = (mapType, newValue, oldValue, major) => dispatch(setColorAction(mapType, newValue, oldValue)); }; -export const setWarningIcon = (newValue, oldValue) => (dispatch, getState) => { +export const setWarningIcon = (newValue, oldValue, skipTracking) => (dispatch, getState) => { dispatch(setNglViewParams(COMMON_PARAMS.warningIcon, newValue)); - dispatch(setWarningIconAction(newValue, oldValue)); + dispatch(setWarningIconAction(newValue, oldValue, skipTracking)); }; const updateDensityMapByType = (type, stage, key, newValue) => (dispatch, getState) => { @@ -328,7 +344,14 @@ export const isDensityMapVisible = (type, stage) => { }; export const restoreNglOrientation = (orientation, oldOrientation, div_id, stages) => (dispatch, getState) => { - const view = stages.find(view => view.id === div_id); - view.stage.viewerControls.orient(orientation); - dispatch(setNglOrientationByInteraction(orientation, oldOrientation, div_id)); + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + + if (!skipOrientation) { + const view = stages.find(view => view.id === div_id); + console.count(`Before restoring orientation - restoreNglOrientation`); + view.stage.viewerControls.orient(orientation); + console.count(`After restoring orientation - restoreNglOrientation`); + dispatch(setNglOrientationByInteraction(orientation, oldOrientation, div_id)); + } }; diff --git a/js/reducers/nglTracking/dispatchActions.js b/js/reducers/nglTracking/dispatchActions.js index f8ef2d721..7cbb6278e 100644 --- a/js/reducers/nglTracking/dispatchActions.js +++ b/js/reducers/nglTracking/dispatchActions.js @@ -690,17 +690,24 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, } }; -const restoreNglStateAction = (orderedActionList, stages) => dispatch => { - let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); - let action = [...actions].pop(); - if (action && action.nglStateList) { - action.nglStateList.forEach(nglView => { - dispatch(setOrientation(nglView.id, nglView.orientation)); - let viewStage = stages.find(s => s.id === nglView.id); - if (viewStage) { - viewStage.stage.viewerControls.orient(nglView.orientation.elements); - } - }); +const restoreNglStateAction = (orderedActionList, stages) => (dispatch, getState) => { + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + + if (!skipOrientation) { + let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); + let action = [...actions].pop(); + if (action && action.nglStateList) { + action.nglStateList.forEach(nglView => { + dispatch(setOrientation(nglView.id, nglView.orientation)); + let viewStage = stages.find(s => s.id === nglView.id); + if (viewStage) { + console.count(`Before restoring orientation - restoreNglStateAction - nglTracking`); + viewStage.stage.viewerControls.orient(nglView.orientation.elements); + console.count(`After restoring orientation - restoreNglStateAction - nglTracking`); + } + }); + } } }; diff --git a/js/reducers/nglTracking/nglTrackingMiddleware.js b/js/reducers/nglTracking/nglTrackingMiddleware.js index 0a95b5a5f..8fdf48d3d 100644 --- a/js/reducers/nglTracking/nglTrackingMiddleware.js +++ b/js/reducers/nglTracking/nglTrackingMiddleware.js @@ -1,6 +1,7 @@ import { appendAndSendTrackingActions } from './dispatchActions'; import { constants } from './constants'; import { findTrackAction } from './trackingActions'; +import { setIsSnapshotDirty } from '../tracking/actions'; const nglTrackingMiddleware = ({ dispatch, getState }) => next => action => { //console.log(`Redux Log:`, action); @@ -10,6 +11,10 @@ const nglTrackingMiddleware = ({ dispatch, getState }) => next => action => { if (action && action.type && !action.type.includes(constants.APPEND_ACTIONS_LIST)) { let trackAction = findTrackAction(action, state); if (trackAction && trackAction != null) { + const isSnapshotDirty = state.trackingReducers.isSnapshotDirty; + if (!isSnapshotDirty) { + dispatch(setIsSnapshotDirty(true)); + } dispatch(appendAndSendTrackingActions(trackAction)); } } diff --git a/js/reducers/selection/selectionReducers.js b/js/reducers/selection/selectionReducers.js index dd65d9e66..297b01ef7 100644 --- a/js/reducers/selection/selectionReducers.js +++ b/js/reducers/selection/selectionReducers.js @@ -292,7 +292,34 @@ export function selectionReducers(state = INITIAL_STATE, action = {}) { }); case constants.RESET_SELECTION_STATE: - return INITIAL_STATE; + const { + vector_list, + fragmentDisplayList, + proteinList, + complexList, + surfaceList, + densityList, + densityListType, + densityListCustom, + qualityList, + vectorOnList, + informationList + } = state; + const newState = { + ...INITIAL_STATE, + vector_list, + fragmentDisplayList, + proteinList, + complexList, + surfaceList, + densityList, + densityListType, + densityListCustom, + qualityList, + vectorOnList, + informationList + }; + return newState; case constants.SET_MOL_GROUP_SELECTION: return Object.assign({}, state, { diff --git a/js/reducers/tracking/actions.js b/js/reducers/tracking/actions.js index baee43b7a..91f9c7ec8 100644 --- a/js/reducers/tracking/actions.js +++ b/js/reducers/tracking/actions.js @@ -137,3 +137,17 @@ export const setTrackingImageSource = imageSource => ({ type: constants.SET_TRACKING_IMAGE_SOURCE, payload: imageSource }); + +export const setSkipOrientationChange = skipOrientationChange => ({ + type: constants.SET_SKIP_ORIENTATION_CHANGE, + skipOrientationChange: skipOrientationChange +}); + +export const setIsSnapshotDirty = isSnapshotDirty => { + // console.count(`setIsSnapshotDirty with value ${isSnapshotDirty}`); + return { + type: constants.SET_IS_SNAPSHOT_DIRTY, + isSnapshotDirty: isSnapshotDirty, + skipTracking: true + }; +}; diff --git a/js/reducers/tracking/constants.js b/js/reducers/tracking/constants.js index 78c05b53f..897b1eea0 100644 --- a/js/reducers/tracking/constants.js +++ b/js/reducers/tracking/constants.js @@ -20,7 +20,9 @@ export const constants = { SET_SNAPSOT_IMAGE_ACTIONS_LIST: prefix + 'SET_SNAPSOT_IMAGE_ACTIONS_LIST', APPEND_UNDO_REDO_ACTIONS_LIST: prefix + 'APPEND_UNDO_REDO_ACTIONS_LIST', SET_UNDO_REDO_ACTIONS_LIST: prefix + 'SET_UNDO_REDO_ACTIONS_LIST', - SET_PROJECT_ACTIONS_LIST_LOADED: prefix + 'SET_PROJECT_ACTIONS_LIST_LOADED' + SET_PROJECT_ACTIONS_LIST_LOADED: prefix + 'SET_PROJECT_ACTIONS_LIST_LOADED', + SET_SKIP_ORIENTATION_CHANGE: prefix + 'SET_SKIP_ORIENTATION_CHANGE', //when snapshot is switched we want to skip orientation change so it doesn't blink through multiple states + SET_IS_SNAPSHOT_DIRTY: prefix + 'SET_IS_SNAPSHOT_DIRTY' }; export const actionType = { @@ -109,6 +111,19 @@ export const actionType = { ALL_MOLECULES_UNSELECTED: 'ALL_MOLECULES_UNSELECTED' }; +export const snapshotSwitchManualActions = [ + actionType.LIGAND_TURNED_ON, + actionType.SIDECHAINS_TURNED_ON, + actionType.SURFACE_TURNED_ON, + actionType.DENSITY_TURNED_ON, + actionType.DENSITY_CUSTOM_TURNED_ON, + // actionType.DENSITY_TYPE_ON, //I don't know what this one is for but it's not processed anywhere + actionType.QUALITY_TURNED_OFF, + actionType.VECTORS_TURNED_ON, + actionType.INTERACTIONS_TURNED_ON, + actionType.ALL_TURNED_ON +]; + export const actionDescription = { LOADED: 'was loaded', TURNED_ON: 'was turned on', diff --git a/js/reducers/tracking/dispatchActions.js b/js/reducers/tracking/dispatchActions.js index e7d35d93f..13fdfadb1 100644 --- a/js/reducers/tracking/dispatchActions.js +++ b/js/reducers/tracking/dispatchActions.js @@ -3,7 +3,8 @@ import { setIsTrackingMoleculesRestoring, setIsTrackingCompoundsRestoring, setIsUndoRedoAction, - setProjectActionListLoaded + setProjectActionListLoaded, + setIsSnapshotDirty } from './actions'; import { createInitAction } from './trackingActions'; import { actionType, actionObjectType, NUM_OF_SECONDS_TO_IGNORE_MERGE, mapTypesStrings } from './constants'; @@ -13,15 +14,14 @@ import { setArrowUpDown, appendToMolListToEdit, removeFromMolListToEdit, - setNextXMolecules, - resetSelectionState + setNextXMolecules } from '../selection/actions'; import { resetReducersForRestoringActions, shouldLoadProtein, loadProteinOfRestoringActions } from '../../components/preview/redux/dispatchActions'; -import { setCurrentProject, setCurrentSnapshot } from '../../components/projects/redux/actions'; +import { setCurrentProject } from '../../components/projects/redux/actions'; import { loadMoleculeGroupsOfTarget } from '../../components/preview/moleculeGroups/redux/dispatchActions'; import { loadTargetList } from '../../components/target/redux/dispatchActions'; import { resetTargetState, setTargetOn } from '../api/actions'; @@ -85,22 +85,17 @@ import { updateFilterShowedScoreProperties, setFilterShowedScoreProperties, setDragDropState, - resetDragDropState, - resetDatasetsStateOnSnapshotChange, resetDatasetScrolledMap } from '../../components/datasets/redux/actions'; -import { getUrl, loadAllMolsFromMolGroup } from '../../../js/utils/genericList'; import { removeComponentRepresentation, addComponentRepresentation, updateComponentRepresentation, updateComponentRepresentationVisibility, updateComponentRepresentationVisibilityAll, - changeComponentRepresentation, - removeAllNglComponents + changeComponentRepresentation } from '../../../js/reducers/ngl/actions'; import { NGL_PARAMS, NGL_VIEW_DEFAULT_VALUES, COMMON_PARAMS } from '../../components/nglView/constants'; -import * as listType from '../../constants/listTypes'; import { assignRepresentationToComp } from '../../components/nglView/generatingObjects'; import { deleteObject, @@ -127,7 +122,7 @@ import { setUndoRedoActionList } from './actions'; import { api, METHOD } from '../../../js/utils/api'; -import { base_url, URLS } from '../../components/routes/constants'; +import { base_url } from '../../components/routes/constants'; import { CONSTANTS } from '../../../js/constants/constants'; import moment from 'moment'; import { @@ -160,12 +155,10 @@ import { MAP_TYPE } from '../ngl/constants'; import { removeSelectedTag, addSelectedTag, - loadMoleculesAndTags, - getMoleculeForId + loadMoleculesAndTags } from '../../components/preview/tags/redux/dispatchActions'; -import { resetViewerControlsState, turnSide } from '../../components/preview/viewerControls/redux/actions'; -import { NetworkCheckSharp } from '@material-ui/icons'; -import { resetNglTrackingState } from '../nglTracking/dispatchActions'; +import { turnSide } from '../../components/preview/viewerControls/redux/actions'; +import { getQualityOffActions } from './utils'; export const addCurrentActionsListToSnapshot = (snapshot, project, nglViewList) => async (dispatch, getState) => { let projectID = project && project.projectID; @@ -324,12 +317,13 @@ const saveActionsList = (project, snapshot, actionList, nglViewList) => async (d getCollection(currentSurfaces), currentActions ); - getCurrentActionList( - orderedActionList, - actionType.QUALITY_TURNED_ON, - getCollection(currentQualities), - currentActions - ); + dispatch(getQualityOffActions(orderedActionList, currentActions)); + // getCurrentActionList( + // orderedActionList, + // actionType.QUALITY_TURNED_OFF, + // getCollection(currentQualities), + // currentActions + // ); getCurrentActionList( orderedActionList, actionType.DENSITY_TURNED_ON, @@ -454,6 +448,12 @@ const saveActionsList = (project, snapshot, actionList, nglViewList) => async (d getCommonLastActionByType(orderedActionList, actionType.CONTOUR_DIFF, currentActions); getCommonLastActionByType(orderedActionList, actionType.COLOR_DIFF, currentActions); getCommonLastActionByType(orderedActionList, actionType.WARNING_ICON, currentActions); + getCommonLastActionByType(orderedActionList, actionType.BACKGROUND_COLOR_CHANGED, currentActions); + getCommonLastActionByType(orderedActionList, actionType.CLIP_NEAR, currentActions); + getCommonLastActionByType(orderedActionList, actionType.CLIP_FAR, currentActions); + getCommonLastActionByType(orderedActionList, actionType.CLIP_DIST, currentActions); + getCommonLastActionByType(orderedActionList, actionType.FOG_NEAR, currentActions); + getCommonLastActionByType(orderedActionList, actionType.FOG_FAR, currentActions); // Since drag and drop state can be influenced by filter as well, determine its state by the last influential action const action = orderedActionList.find(action => @@ -830,7 +830,10 @@ export const restoreStateBySavedActionList = () => (dispatch, getState) => { let onCancel = () => {}; dispatch(loadTargetList(onCancel)) - .then(() => dispatch(restoreTargetActions(orderedActionList))) + .then(() => { + dispatch(restoreTargetActions(orderedActionList)); + dispatch(setIsSnapshotDirty(false)); + }) .catch(error => { throw new Error(error); }); @@ -890,45 +893,11 @@ export const restoreAfterTargetActions = (stages, projectId) => async (dispatch, dispatch(setIsActionsRestoring(false, true)); dispatch(restoreViewerControlActions(orderedActionList)); dispatch(resetDatasetScrolledMap()); // Have a look at useScrollToSelected.js + dispatch(setIsSnapshotDirty(false)); } }; -/** - * The goal of this method is to restore the state of the app based on the tracking - * action. All of the dispatched actions were added by trial and error - in case you - * need to alter, remove or add anything, you should properly test the changes. - */ -export const restoreAfterSnapshotChange = (stages, projectId) => async (dispatch, getState) => { - const state = getState(); - - const currentActionList = state.trackingReducers.current_actions_list; - const orderedActionList = currentActionList.sort((a, b) => a.timestamp - b.timestamp); - const targetId = state.apiReducers.target_on; - - if (targetId && stages && stages.length > 0) { - const majorView = stages.find(view => view.id === VIEWS.MAJOR_VIEW); - - dispatch(restoreViewerControlActions(orderedActionList)); - - await dispatch(loadProteinOfRestoringActions({ nglViewList: stages })); - - await dispatch(restoreSitesActions(orderedActionList)); - await dispatch(restoreTagActions(orderedActionList)); - await dispatch(restoreMoleculesActions(orderedActionList, majorView.stage)); - await dispatch(restoreRepresentationActions(orderedActionList, stages)); - dispatch(restoreMoleculeSelectionActions(orderedActionList)); - dispatch(restoreTabActions(orderedActionList)); - await dispatch(restoreCartActions(orderedActionList, majorView.stage)); - dispatch(restoreSnapshotImageActions(projectId)); - dispatch(restoreNglStateAction(orderedActionList, stages)); - dispatch(restoreNglSettingsAction(orderedActionList, majorView.stage)); - dispatch(restoreCompoundsActions(orderedActionList, majorView.stage)); - dispatch(resetDatasetScrolledMap()); // Have a look at useScrollToSelected.js - dispatch(setIsActionsRestoring(false, true)); - } -}; - -const restoreMoleculeSelectionActions = orderedActionList => (dispatch, getState) => { +export const restoreMoleculeSelectionActions = orderedActionList => (dispatch, getState) => { const state = getState(); let actions = orderedActionList.filter(action => action.type === actionType.MOLECULE_SELECTED); if (actions) { @@ -945,7 +914,7 @@ export const restoreNglViewSettings = stages => (dispatch, getState) => { dispatch(restoreNglSettingsAction([], majorViewStage)); }; -const restoreNglSettingsAction = (orderedActionList, majorViewStage) => (dispatch, getState) => { +export const restoreNglSettingsAction = (orderedActionList, majorViewStage) => (dispatch, getState) => { const state = getState(); const viewParams = state.nglReducers.viewParams; @@ -1252,23 +1221,32 @@ const restoreNglSettingsAction = (orderedActionList, majorViewStage) => (dispatc let warningIconAction = orderedActionList.find(action => action.type === actionType.WARNING_ICON); if (warningIconAction && warningIconAction.newSetting !== undefined) { let value = warningIconAction.newSetting; - dispatch(setWarningIcon(value, viewParams[COMMON_PARAMS.warningIcon])); + dispatch(setWarningIcon(value, viewParams[COMMON_PARAMS.warningIcon], true)); } else { - dispatch(setWarningIcon(NGL_VIEW_DEFAULT_VALUES[COMMON_PARAMS.warningIcon], viewParams[COMMON_PARAMS.warningIcon])); + dispatch( + setWarningIcon(NGL_VIEW_DEFAULT_VALUES[COMMON_PARAMS.warningIcon], viewParams[COMMON_PARAMS.warningIcon], true) + ); } }; -const restoreNglStateAction = (orderedActionList, stages) => (dispatch, getState) => { - let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); - let action = [...actions].pop(); - if (action && action.nglStateList) { - action.nglStateList.forEach(nglView => { - dispatch(setOrientation(nglView.id, nglView.orientation)); - let viewStage = stages.find(s => s.id === nglView.id); - if (viewStage) { - viewStage.stage.viewerControls.orient(nglView.orientation.elements); - } - }); +export const restoreNglStateAction = (orderedActionList, stages) => (dispatch, getState) => { + const state = getState(); + const skipOrientation = state.trackingReducers.skipOrientationChange; + + if (!skipOrientation) { + let actions = orderedActionList.filter(action => action.type === actionType.NGL_STATE); + let action = [...actions].pop(); + if (action && action.nglStateList) { + action.nglStateList.forEach(nglView => { + dispatch(setOrientation(nglView.id, nglView.orientation)); + let viewStage = stages.find(s => s.id === nglView.id); + if (viewStage) { + console.count(`Before restoring orientation - restoreNglStateAction - tracking`); + viewStage.stage.viewerControls.orient(nglView.orientation.elements); + console.count(`After restoring orientation - restoreNglStateAction - tracking`); + } + }); + } } }; @@ -1291,7 +1269,7 @@ const loadAllMolecules = target_on => async (dispatch, getState) => { await dispatch(loadMoleculesAndTags(target_on)); }; -const restoreSitesActions = orderedActionList => (dispatch, getState) => { +export const restoreSitesActions = orderedActionList => (dispatch, getState) => { const state = getState(); let sitesAction = orderedActionList.filter(action => action.type === actionType.SITE_TURNED_ON); @@ -1306,7 +1284,7 @@ const restoreSitesActions = orderedActionList => (dispatch, getState) => { } }; -const restoreTagActions = orderedActionList => (dispatch, getState) => { +export const restoreTagActions = orderedActionList => (dispatch, getState) => { const state = getState(); let tagActions = orderedActionList.filter(action => action.type === actionType.TAG_SELECTED); @@ -1321,7 +1299,7 @@ const restoreTagActions = orderedActionList => (dispatch, getState) => { } }; -const restoreMoleculesActions = (orderedActionList, stage) => async (dispatch, getState) => { +export const restoreMoleculesActions = (orderedActionList, stage) => async (dispatch, getState) => { const state = getState(); let moleculesAction = orderedActionList.filter( action => action.object_type === actionObjectType.MOLECULE || action.object_type === actionObjectType.INSPIRATION @@ -1332,7 +1310,7 @@ const restoreMoleculesActions = (orderedActionList, stage) => async (dispatch, g await dispatch(addNewType(moleculesAction, actionType.SIDECHAINS_TURNED_ON, 'protein', stage, state)); await dispatch(addNewType(moleculesAction, actionType.INTERACTIONS_TURNED_ON, 'complex', stage, state)); await dispatch(addNewType(moleculesAction, actionType.SURFACE_TURNED_ON, 'surface', stage, state)); - await dispatch(addNewType(moleculesAction, actionType.QUALITY_TURNED_ON, 'quality', stage, state)); + await dispatch(addNewType(moleculesAction, actionType.QUALITY_TURNED_OFF, 'quality', stage, state)); await dispatch(addNewType(moleculesAction, actionType.VECTORS_TURNED_ON, 'vector', stage, state)); await dispatch(addNewType(moleculesAction, actionType.DENSITY_TURNED_ON, 'density', stage, state)); await dispatch(addNewType(moleculesAction, actionType.DENSITY_TYPE_ON, 'density', stage, state)); @@ -1345,7 +1323,7 @@ const restoreMoleculesActions = (orderedActionList, stage) => async (dispatch, g dispatch(setIsTrackingMoleculesRestoring(false)); }; -const restoreCartActions = (orderedActionList, majorViewStage) => async (dispatch, getState) => { +export const restoreCartActions = (orderedActionList, majorViewStage) => async (dispatch, getState) => { let vectorAction = orderedActionList.find(action => action.type === actionType.VECTOR_SELECTED); if (vectorAction) { await dispatch(selectVectorAndResetCompounds(vectorAction.object_name)); @@ -1559,7 +1537,7 @@ const restoreAllSelectionByTypeActions = (moleculesAction, stage, isSelection) = } }; -const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, getState) => { +export const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, getState) => { const nglView = stages.find(view => view.id === VIEWS.MAJOR_VIEW); let representationsActions = moleculesAction.filter(action => action.type === actionType.REPRESENTATION_ADDED); @@ -1581,7 +1559,7 @@ const restoreRepresentationActions = (moleculesAction, stages) => (dispatch, get } }; -const restoreTabActions = moleculesAction => (dispatch, getState) => { +export const restoreTabActions = moleculesAction => (dispatch, getState) => { const state = getState(); const customDatasets = state.datasetsReducers.datasets; let firstCustomDatasetTitle = (customDatasets && customDatasets[0] && customDatasets[0].title) || ''; @@ -1652,7 +1630,7 @@ const restoreTabActions = moleculesAction => (dispatch, getState) => { } }; -const restoreViewerControlActions = moleculesAction => dispatch => { +export const restoreViewerControlActions = moleculesAction => dispatch => { const turnSideActions = moleculesAction.filter(action => action.type === actionType.TURN_SIDE); turnSideActions.forEach(action => { const { side, open } = action; @@ -1660,7 +1638,7 @@ const restoreViewerControlActions = moleculesAction => dispatch => { }); }; -const restoreSnapshotImageActions = projectID => async (dispatch, getState) => { +export const restoreSnapshotImageActions = projectID => async (dispatch, getState) => { const state = getState(); const isProjectActionListLoaded = state.trackingReducers.isProjectActionListLoaded; if (!isProjectActionListLoaded) { @@ -1698,7 +1676,7 @@ const restoreProject = projectId => (dispatch, getState) => { } }; -const restoreCompoundsActions = (orderedActionList, stage) => (dispatch, getState) => { +export const restoreCompoundsActions = (orderedActionList, stage) => (dispatch, getState) => { const state = getState(); let compoundsAction = orderedActionList.filter( @@ -1745,6 +1723,23 @@ const addTypeCompound = { surface: addDatasetSurface }; +const removeType = { + ligand: removeLigand, + protein: removeHitProtein, + complex: removeComplex, + surface: removeSurface, + density: removeDensity, + quality: removeQuality, + vector: removeVector +}; + +const removeTypeCompound = { + ligand: removeDatasetLigand, + protein: removeDatasetHitProtein, + complex: removeDatasetComplex, + surface: removeDatasetSurface +}; + const addNewType = (moleculesAction, actionType, type, stage, state, skipTracking = false) => async dispatch => { let actions = moleculesAction.filter(action => action.type === actionType); if (actions) { @@ -1777,8 +1772,10 @@ const addNewType = (moleculesAction, actionType, type, stage, state, skipTrackin data.proteinData.render_quality = !!action.render_quality; await dispatch(addType[type](stage, data, colourList[data.id % colourList.length], true, skipTracking)); } + } else if (type === 'quality') { + await dispatch(removeType[type](stage, data, colourList[data.id % colourList.length], skipTracking)); } else { - dispatch(addType[type](stage, data, colourList[data.id % colourList.length], skipTracking)); + await dispatch(addType[type](stage, data, colourList[data.id % colourList.length], skipTracking)); } } } @@ -1833,19 +1830,13 @@ const getTarget = (targetName, state) => { return target; }; -const getMolGroup = (molGroupName, state) => { - let molGroupList = state.apiReducers.mol_group_list; - let molGroup = molGroupList.find(group => group.description === molGroupName); - return molGroup; -}; - const getTag = (tagId, state) => { const tagList = state.apiReducers.tagList; const tag = tagList.find(t => t.id === tagId); return tag; }; -const getMolecule = (moleculeName, state) => { +export const getMolecule = (moleculeName, state) => { let moleculeList = state.apiReducers.all_mol_lists; let molecule = null; @@ -1854,7 +1845,7 @@ const getMolecule = (moleculeName, state) => { return molecule; }; -const getCompound = (action, state) => { +export const getCompound = (action, state) => { let moleculeList = state.datasetsReducers.moleculeLists; let molecule = null; @@ -1870,7 +1861,7 @@ const getCompound = (action, state) => { return molecule; }; -const getCompoundByName = (name, datasetID, state) => { +export const getCompoundByName = (name, datasetID, state) => { let moleculeList = state.datasetsReducers.moleculeLists; let molecule = null; @@ -3158,23 +3149,6 @@ const handleMoleculeAction = (action, type, isAdd, stage, state, skipTracking) = } }; -const removeType = { - ligand: removeLigand, - protein: removeHitProtein, - complex: removeComplex, - surface: removeSurface, - density: removeDensity, - quality: removeQuality, - vector: removeVector -}; - -const removeTypeCompound = { - ligand: removeDatasetLigand, - protein: removeDatasetHitProtein, - complex: removeDatasetComplex, - surface: removeDatasetSurface -}; - const removeNewType = (action, type, stage, state, skipTracking) => dispatch => { if (action) { let data = getMolecule(action.object_name, state); @@ -3563,52 +3537,3 @@ export const setAndUpdateTrackingActions = (actionList, projectID) => (dispatch, return Promise.resolve(); } }; - -/** - * The goal of this method is to change the snapshot without reloading the page. - * All of the dispatched actions were added by trial and error - in case you need - * to alter, remove or add anything, you should properly test the changes. - */ -export const changeSnapshot = (projectID, snapshotID, nglViewList, stage) => async (dispatch, getState) => { - // A hacky way of changing the URL without triggering react-router - window.history.replaceState(null, null, `${URLS.projects}${projectID}/${snapshotID}`); - - // Load the needed data - const snapshotResponse = await api({ url: `${base_url}/api/snapshots/${snapshotID}` }); - const actionsResponse = await api({ - url: `${base_url}/api/snapshot-actions/?snapshot=${snapshotID}` - }); - - dispatch( - setCurrentSnapshot({ - id: snapshotResponse.data.id, - type: snapshotResponse.data.type, - title: snapshotResponse.data.title, - author: snapshotResponse.data.author, - description: snapshotResponse.data.description, - created: snapshotResponse.data.created, - children: snapshotResponse.data.children, - parent: snapshotResponse.data.parent, - data: snapshotResponse.data.data - }) - ); - - let results = actionsResponse.data.results; - let listToSet = []; - results.forEach(r => { - let resultActions = JSON.parse(r.actions); - listToSet.push(...resultActions); - }); - let snapshotActions = [...listToSet]; - dispatch(setCurrentActionsList(snapshotActions)); - - dispatch(resetSelectionState()); - dispatch(resetDatasetsStateOnSnapshotChange()); - dispatch(resetViewerControlsState()); - dispatch(resetNglTrackingState()); - - dispatch(removeAllNglComponents(stage)); - - dispatch(restoreStateBySavedActionList()); - dispatch(restoreAfterSnapshotChange(nglViewList, projectID)); -}; diff --git a/js/reducers/tracking/dispatchActionsSwitchSnapshot.js b/js/reducers/tracking/dispatchActionsSwitchSnapshot.js new file mode 100644 index 000000000..8c9a8f6f7 --- /dev/null +++ b/js/reducers/tracking/dispatchActionsSwitchSnapshot.js @@ -0,0 +1,539 @@ +import { base_url, URLS } from '../../components/routes/constants'; +import { api } from '../../../js/utils/api'; +import { setCurrentSnapshot } from '../../components/projects/redux/actions'; +import { setCurrentActionsList, setIsActionsRestoring, setSkipOrientationChange, setIsSnapshotDirty } from './actions'; +import { resetSelectionState } from '../selection/actions'; +import { resetDatasetsStateOnSnapshotChange, resetDatasetScrolledMap } from '../../components/datasets/redux/actions'; +import { resetViewerControlsState } from '../../components/preview/viewerControls/redux/actions'; +import { resetNglTrackingState } from '../nglTracking/dispatchActions'; +import { removeAllNglComponents } from '../ngl/actions'; +import { + restoreStateBySavedActionList, + restoreViewerControlActions, + restoreSitesActions, + restoreTagActions, + restoreMoleculesActions, + restoreRepresentationActions, + restoreMoleculeSelectionActions, + restoreTabActions, + restoreCartActions, + restoreSnapshotImageActions, + restoreNglStateAction, + restoreNglSettingsAction, + restoreCompoundsActions, + getMolecule, + getCompound +} from './dispatchActions'; +import { VIEWS } from '../../../js/constants/constants'; +import { loadProteinOfRestoringActions } from '../../components/preview/redux/dispatchActions'; +import { actionType, snapshotSwitchManualActions, actionObjectType } from './constants'; +import { getMoleculeForId } from '../../components/preview/tags/redux/dispatchActions'; +import { + addLigand, + removeLigand, + removeHitProtein, + addHitProtein, + removeSurface, + addSurface, + removeQuality, + addQuality, + removeComplex, + addComplex, + removeVector, + addVector, + removeDensity, + addDensity, + addDensityCustomView +} from '../../components/preview/molecule/redux/dispatchActions'; +import { getRandomColor } from '../../components/preview/molecule/utils/color'; +import { + addDatasetLigand, + removeDatasetLigand, + removeDatasetHitProtein, + addDatasetHitProtein, + removeDatasetSurface, + addDatasetSurface, + removeDatasetComplex, + addDatasetComplex +} from '../../components/datasets/redux/dispatchActions'; +import { getDifference } from './utils'; + +/** + * The goal of this method is to restore the state of the app based on the tracking + * action. All of the dispatched actions were added by trial and error - in case you + * need to alter, remove or add anything, you should properly test the changes. + */ +export const restoreAfterSnapshotChange = (stages, projectId) => async (dispatch, getState) => { + console.count(`restoreAfterSnapshotChange start`); + const state = getState(); + + const currentActionList = state.trackingReducers.current_actions_list; + const filteredActionList = currentActionList.filter(action => !snapshotSwitchManualActions.includes(action.type)); + const orderedActionList = filteredActionList.sort((a, b) => a.timestamp - b.timestamp); + const targetId = state.apiReducers.target_on; + + if (targetId && stages && stages.length > 0) { + dispatch(setSkipOrientationChange(true)); + const majorView = stages.find(view => view.id === VIEWS.MAJOR_VIEW); + + dispatch(restoreViewerControlActions(orderedActionList)); + + // await dispatch(loadProteinOfRestoringActions({ nglViewList: stages })); + + await dispatch(restoreSitesActions(orderedActionList)); + await dispatch(restoreTagActions(orderedActionList)); + await dispatch(restoreMoleculesActions(orderedActionList, majorView.stage)); + + await dispatch(handleLigandsOfMols(currentActionList, majorView.stage)); + await dispatch(handleProteinsOfMols(currentActionList, majorView.stage)); + await dispatch(handleComplexesOfMols(currentActionList, majorView.stage)); + await dispatch(handleShowAllOfMols(currentActionList, majorView.stage)); + await dispatch(handleSurfacesOfMols(currentActionList, majorView.stage)); + await dispatch(handleQualityOfMols(currentActionList, majorView.stage)); + await dispatch(handleVectorsOfMols(currentActionList, majorView.stage)); + await dispatch(handleDensityOfMols(currentActionList, majorView.stage)); + // await dispatch(handleDensityTypeOfMols(currentActionList, majorView.stage)); + await dispatch(handleCustomDensityOfMols(currentActionList, majorView.stage)); + + await dispatch(restoreRepresentationActions(orderedActionList, stages)); + await dispatch(restoreMoleculeSelectionActions(orderedActionList)); + await dispatch(restoreTabActions(orderedActionList)); + await dispatch(restoreCartActions(orderedActionList, majorView.stage)); + await dispatch(restoreSnapshotImageActions(projectId)); + // console.count(`BEFORE restoration orientation from snapshot`); + // dispatch(restoreNglStateAction(orderedActionList, stages)); + // console.count(`AFTER restoration orientation from snapshot`); + await dispatch(restoreNglSettingsAction(orderedActionList, majorView.stage)); + // dispatch(restoreCompoundsActions(orderedActionList, majorView.stage)); + + await dispatch(handleLigandsOfCompounds(currentActionList, majorView.stage)); + await dispatch(handleProteinsOfCompounds(currentActionList, majorView.stage)); + await dispatch(handleComplexesOfCompounds(currentActionList, majorView.stage)); + await dispatch(handleSurfacesOfCompounds(currentActionList, majorView.stage)); + await dispatch(handleShowAllOfCompounds(currentActionList, majorView.stage)); + await dispatch(restoreCompoundsActions(orderedActionList, majorView.stage)); + + dispatch(setSkipOrientationChange(false)); + console.count(`BEFORE restoration orientation from snapshot`); + await dispatch(restoreNglStateAction(orderedActionList, stages)); + console.count(`AFTER restoration orientation from snapshot`); + + dispatch(resetDatasetScrolledMap()); // Have a look at useScrollToSelected.js + dispatch(setIsActionsRestoring(false, true)); + + console.count(`restoreAfterSnapshotChange end`); + } +}; + +/** + * The goal of this method is to change the snapshot without reloading the page. + * All of the dispatched actions were added by trial and error - in case you need + * to alter, remove or add anything, you should properly test the changes. + */ +export const changeSnapshot = (projectID, snapshotID, nglViewList, stage) => async (dispatch, getState) => { + console.count(`Change snapshot - start`); + // A hacky way of changing the URL without triggering react-router + window.history.replaceState(null, null, `${URLS.projects}${projectID}/${snapshotID}`); + + // Load the needed data + const snapshotResponse = await api({ url: `${base_url}/api/snapshots/${snapshotID}` }); + const actionsResponse = await api({ + url: `${base_url}/api/snapshot-actions/?snapshot=${snapshotID}` + }); + + dispatch( + setCurrentSnapshot({ + id: snapshotResponse.data.id, + type: snapshotResponse.data.type, + title: snapshotResponse.data.title, + author: snapshotResponse.data.author, + description: snapshotResponse.data.description, + created: snapshotResponse.data.created, + children: snapshotResponse.data.children, + parent: snapshotResponse.data.parent, + data: snapshotResponse.data.data + }) + ); + + let results = actionsResponse.data.results; + let listToSet = []; + results.forEach(r => { + let resultActions = JSON.parse(r.actions); + listToSet.push(...resultActions); + }); + let snapshotActions = [...listToSet]; + dispatch(setCurrentActionsList(snapshotActions)); + + dispatch(resetSelectionState()); //here what is visible from the LHS is reset + dispatch(resetDatasetsStateOnSnapshotChange()); //here what is visible from RHS is reset + dispatch(resetViewerControlsState()); //LHS and RHS is/isn't visible + dispatch(resetNglTrackingState()); //???? + + // dispatch(removeAllNglComponents(stage)); //this removes everything from ngl view + + dispatch(restoreStateBySavedActionList()); + await dispatch(restoreAfterSnapshotChange(nglViewList, projectID)); + + dispatch(setIsSnapshotDirty(false)); + + console.count(`Change snapshot - end`); +}; + +//ALL_TURNED_ON is a mass action so it needs special treatment +const handleShowAllOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const molIds = dispatch(getMoleculeIdsFromActions(actions, actionType.ALL_TURNED_ON)); + const ligands = state.selectionReducers.fragmentDisplayList; + const proteins = state.selectionReducers.proteinList; + const complexes = state.selectionReducers.complexList; + + for (const molId of molIds) { + const mol = dispatch(getMoleculeForId(molId.id)); + const action = actions.find( + a => + a.type === actionType.ALL_TURNED_ON && a.object_id === molId.id && a.object_type === actionObjectType.MOLECULE + ); + if (action.isLigand && !ligands.find(l => l === molId.id)) { + await dispatch(addLigand(stage, mol, getRandomColor(mol), false, true, true)); + } + if (action.isProtein && !proteins.find(p => p === molId.id)) { + await dispatch(addHitProtein(stage, mol, getRandomColor(mol), true, true)); + } + if (action.isComplex && !complexes.find(c => c === molId.id)) { + await dispatch(addComplex(stage, mol, getRandomColor(mol), true)); + } + } +}; + +//ALL_TURNED_ON is a mass action so it needs special treatment +const handleShowAllOfCompounds = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const compounds = dispatch(getCompoundsIdsFromActions(actions, actionType.ALL_TURNED_ON)); + const ligands = state.datasetsReducers.ligandLists; + const proteins = state.datasetsReducers.proteinLists; + const complexes = state.datasetsReducers.complexLists; + + for (const [datasetId, cmpIds] of Object.entries(compounds)) { + for (const cmpId of cmpIds) { + const cmp = dispatch(getCompoundById(cmpId.id, datasetId)); + const action = actions.find( + a => + a.type === actionType.ALL_TURNED_ON && a.object_id === cmpId.id && a.object_type === actionObjectType.COMPOUND + ); + if (action.isLigand && !ligands[datasetId]?.find(l => l === cmpId.id)) { + await dispatch(addDatasetLigand(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + if (action.isProtein && !proteins[datasetId]?.find(p => p === cmpId.id)) { + await dispatch(addDatasetHitProtein(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + if (action.isComplex && !complexes[datasetId]?.find(c => c === cmpId.id)) { + await dispatch(addDatasetComplex(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + } + } +}; + +const handleLigandsOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.LIGAND_TURNED_ON, state.selectionReducers.fragmentDisplayList) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeLigand(stage, mol, true)); + }); + + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addLigand(stage, mol, getRandomColor(mol), false, true, true)); + } +}; + +const handleLigandsOfCompounds = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForCompounds(actions, actionType.LIGAND_TURNED_ON, state.datasetsReducers.ligandLists) + ); + + for (const [datasetId, cmpIds] of Object.entries(instructions)) { + cmpIds.toTurnOff.forEach(cmpId => { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + dispatch(removeDatasetLigand(stage, cmp, getRandomColor(cmp), datasetId, true)); + }); + for (const cmpId of cmpIds.toTurnOn) { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + await dispatch(addDatasetLigand(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + } +}; + +const handleProteinsOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.SIDECHAINS_TURNED_ON, state.selectionReducers.proteinList) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeHitProtein(stage, mol, getRandomColor(mol), true)); + }); + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addHitProtein(stage, mol, getRandomColor(mol), true, true)); + } +}; + +const handleProteinsOfCompounds = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForCompounds(actions, actionType.SIDECHAINS_TURNED_ON, state.datasetsReducers.proteinLists) + ); + + for (const [datasetId, cmpIds] of Object.entries(instructions)) { + cmpIds.toTurnOff.forEach(cmpId => { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + dispatch(removeDatasetHitProtein(stage, cmp, getRandomColor(cmp), datasetId, true)); + }); + for (const cmpId of cmpIds.toTurnOn) { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + await dispatch(addDatasetHitProtein(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + } +}; + +const handleSurfacesOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.SURFACE_TURNED_ON, state.selectionReducers.surfaceList) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeSurface(stage, mol, getRandomColor(mol), true)); + }); + + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addSurface(stage, mol, getRandomColor(mol), true, true)); + } +}; + +const handleSurfacesOfCompounds = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForCompounds(actions, actionType.SURFACE_TURNED_ON, state.datasetsReducers.surfaceLists) + ); + + for (const [datasetId, cmpIds] of Object.entries(instructions)) { + cmpIds.toTurnOff.forEach(cmpId => { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + dispatch(removeDatasetSurface(stage, cmp, getRandomColor(cmp), datasetId, true)); + }); + for (const cmpId of cmpIds.toTurnOn) { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + await dispatch(addDatasetSurface(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + } +}; + +const handleQualityOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + + //quality is turned on implicitly by turning on ligands so in this case we just need to turn off quality + //for mols which are in the snapshot. Just make sure that ligands are turned on first (and that action is awaited) + let molIds = dispatch(getMoleculeIdsFromActions(actions, actionType.QUALITY_TURNED_OFF)); + molIds.forEach(async molId => { + const mol = dispatch(getMoleculeForId(molId.id)); + await dispatch(removeQuality(stage, mol, getRandomColor(mol), true)); + }); + + //now we need to get all the ligands that should be on and turn on quality for them if the quality + //was off in current snapshot. This is because if ligand is on in both snapshots it's not rerendered + // (which implicitly turns on quality) so we need to render it manually + molIds = dispatch(getMoleculeIdsFromActions(actions, actionType.LIGAND_TURNED_ON)); + const currentlyOnQuality = state.selectionReducers.qualityList; + for (const molId of molIds) { + if (!currentlyOnQuality.find(q => q === molId.id)) { + const mol = dispatch(getMoleculeForId(molId.id)); + await dispatch(addQuality(stage, mol, getRandomColor(mol), true)); + } + } +}; + +const handleComplexesOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.INTERACTIONS_TURNED_ON, state.selectionReducers.complexList) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeComplex(stage, mol, getRandomColor(mol), true)); + }); + + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addComplex(stage, mol, getRandomColor(mol), true)); + } +}; + +const handleComplexesOfCompounds = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForCompounds(actions, actionType.INTERACTIONS_TURNED_ON, state.datasetsReducers.complexLists) + ); + + for (const [datasetId, cmpIds] of Object.entries(instructions)) { + cmpIds.toTurnOff.forEach(cmpId => { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + dispatch(removeDatasetComplex(stage, cmp, getRandomColor(cmp), datasetId, true)); + }); + for (const cmpId of cmpIds.toTurnOn) { + const cmp = dispatch(getCompoundById(cmpId, datasetId)); + await dispatch(addDatasetComplex(stage, cmp, getRandomColor(cmp), datasetId, true)); + } + } +}; + +const handleVectorsOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.VECTORS_TURNED_ON, state.selectionReducers.fragmentDisplayList) //TODO ??????? + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeVector(stage, mol, true)); + }); + + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addVector(stage, mol, true)); + } +}; + +const handleDensityOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols(actions, actionType.DENSITY_TURNED_ON, state.selectionReducers.densityList) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeDensity(stage, mol, getRandomColor(mol), false, true)); + }); + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addDensity(stage, mol, getRandomColor(mol), true, true)); + } +}; + +const handleCustomDensityOfMols = (actions, stage) => async (dispatch, getState) => { + const state = getState(); + const instructions = dispatch( + getRestoreInstructionsForMols( + actions, + actionType.DENSITY_CUSTOM_TURNED_ON, + state.selectionReducers.densityListCustom + ) + ); + + instructions.toTurnOff.forEach(molId => { + const mol = dispatch(getMoleculeForId(molId)); + dispatch(removeDensity(stage, mol, getRandomColor(mol), false, true)); + }); + + for (const molId of instructions.toTurnOn) { + const mol = dispatch(getMoleculeForId(molId)); + await dispatch(addDensityCustomView(stage, mol, getRandomColor(mol), true, true)); + } +}; + +//this one is wrong. If testing shows that we need to handle this in this way then it will need fixing +//it needs to get which maps are enabled from the action and also which is disabled so actually +//the implementation will be more akin to handleShowAllOfMols +const handleDensityTypeOfMols = (actions, stage) => async (dispatch, getState) => {}; + +const getInstructions = (idObjects, visibleIds) => { + const cmpIdsFromActions = idObjects.map(idObject => idObject.id); + const molsToTurnOff = getDifference(visibleIds, cmpIdsFromActions); + const molsToTurnOn = getDifference(cmpIdsFromActions, visibleIds); + + return { toTurnOn: molsToTurnOn, toTurnOff: molsToTurnOff }; +}; + +const getRestoreInstructionsForMols = (actions, actionType, list) => (dispatch, getState) => { + const molIds = dispatch(getMoleculeIdsFromActions(actions, actionType)); + + return getInstructions(molIds, list); +}; + +const getRestoreInstructionsForCompounds = (actions, actionType, compoundsLists) => (dispatch, getState) => { + const state = getState(); + const compounds = dispatch(getCompoundsIdsFromActions(actions, actionType)); + const allDatasets = state.datasetsReducers.datasets; + + const result = {}; + const datasetsVisited = {}; + + Object.entries(compounds).map(([datasetId, cmpIds]) => { + datasetsVisited[datasetId] = true; + const list = compoundsLists[datasetId]; + const instructions = getInstructions(cmpIds, list); + result[datasetId] = instructions; + }); + + //this is here because if someone selectes A button on the compound (RHS which turns on L, P and C) + //whitout ever using button on other compounds from given dataset then we need to turn them off + //this way because turn on and off instructions are not generated for the action and dataset combo which is not in the snapshot + allDatasets.forEach(dataset => { + if (!datasetsVisited.hasOwnProperty(dataset.id) || !datasetsVisited[dataset.id]) { + const list = compoundsLists[dataset.id]; + const instructions = getInstructions([], list); + result[dataset.id] = instructions; + } + }); + + return result; +}; + +const getMoleculeIdsFromActions = (actions, actionType) => (dispatch, getState) => { + const state = getState(); + const result = actions + .filter(action => action.type === actionType && action.object_type === actionObjectType.MOLECULE) + .map(action => { + const mol = getMolecule(action.object_name, state); + return { id: mol?.id, name: mol?.protein_code }; + }); + + return result; +}; + +const getCompoundsIdsFromActions = (actions, actionType) => (dispatch, getState) => { + const state = getState(); + + let cmpOjects = actions + .filter(action => action.type === actionType && action.object_type === actionObjectType.COMPOUND) + .map(action => { + const cmp = getCompound(action, state); + return { datasetId: action.dataset_id, id: cmp?.id, name: cmp.name }; + }); + const result = {}; + cmpOjects.forEach(cmp => { + let datasetCmps = []; + if (result.hasOwnProperty(cmp.datasetId)) { + datasetCmps = result[cmp.datasetId]; + } + datasetCmps = [...datasetCmps, { id: cmp.id, name: cmp.name }]; + result[cmp.datasetId] = datasetCmps; + }); + + return result; +}; + +const getCompoundById = (id, datasetId) => (dispatch, getState) => { + const state = getState(); + const datasetCmps = state.datasetsReducers.moleculeLists[datasetId]; + return datasetCmps?.find(cmp => cmp.id === id); +}; diff --git a/js/reducers/tracking/trackingMiddleware.js b/js/reducers/tracking/trackingMiddleware.js index c3d3ef74c..7b54f0669 100644 --- a/js/reducers/tracking/trackingMiddleware.js +++ b/js/reducers/tracking/trackingMiddleware.js @@ -1,6 +1,7 @@ import { appendAndSendTrackingActions } from './dispatchActions'; import { constants } from './constants'; import { findTrackAction } from './trackingActions'; +import { setIsSnapshotDirty } from './actions'; const trackingMiddleware = ({ dispatch, getState }) => next => action => { //console.log(`Redux Log:`, action); @@ -10,6 +11,10 @@ const trackingMiddleware = ({ dispatch, getState }) => next => action => { if (action && action.type && !action.type.includes(constants.APPEND_ACTIONS_LIST)) { let trackAction = dispatch(findTrackAction(action, state)); if (trackAction && trackAction != null) { + const isSnapshotDirty = state.trackingReducers.isSnapshotDirty; + if (!isSnapshotDirty) { + dispatch(setIsSnapshotDirty(true)); + } dispatch(appendAndSendTrackingActions(trackAction)); } } diff --git a/js/reducers/tracking/trackingReducers.js b/js/reducers/tracking/trackingReducers.js index e4f2dd3fe..89bf88031 100644 --- a/js/reducers/tracking/trackingReducers.js +++ b/js/reducers/tracking/trackingReducers.js @@ -19,7 +19,9 @@ export const INITIAL_STATE = { isActionRestored: false, isActionTracking: false, trackingImageSource: '', - isProjectActionListLoaded: false + isProjectActionListLoaded: false, + skipOrientationChange: false, + isSnapshotDirty: false }; export function trackingReducers(state = INITIAL_STATE, action = {}) { @@ -119,6 +121,12 @@ export function trackingReducers(state = INITIAL_STATE, action = {}) { trackingImageSource: action.payload }); + case constants.SET_SKIP_ORIENTATION_CHANGE: + return { ...state, skipOrientationChange: action.skipOrientationChange }; + + case constants.SET_IS_SNAPSHOT_DIRTY: + return { ...state, isSnapshotDirty: action.isSnapshotDirty }; + case constants.RESET_TRACKING_STATE: return INITIAL_STATE; diff --git a/js/reducers/tracking/utils.js b/js/reducers/tracking/utils.js new file mode 100644 index 000000000..71f70cbf3 --- /dev/null +++ b/js/reducers/tracking/utils.js @@ -0,0 +1,26 @@ +import { actionType } from './constants'; + +export const getQualityOffActions = (orderedActionList, currentActionList) => (dispatch, getState) => { + const state = getState(); + const ligandsOn = state.selectionReducers.fragmentDisplayList; + const qualitiesOn = state.selectionReducers.qualityList; + + const qualitiesToTurnOff = getDifference(ligandsOn, qualitiesOn); + const actionList = orderedActionList.filter(action => action.type === actionType.QUALITY_TURNED_OFF); + + qualitiesToTurnOff.forEach(molId => { + const action = actionList.find(action => action.object_id === molId); + + if (action) { + currentActionList.push({ ...action }); + } + }); +}; + +/** + * The goal of this method is to compare two arrays of primitive values and return the difference. + */ +export const getDifference = (array1, array2) => { + const result = array1.filter(x => !array2.includes(x)); + return result; +}; diff --git a/js/utils/discourse.js b/js/utils/discourse.js index 7685d4e55..3d126790e 100644 --- a/js/utils/discourse.js +++ b/js/utils/discourse.js @@ -52,7 +52,6 @@ export const createProjectPost = (projectName, targetName, msg, tags) => { post_content: msg, post_tags: JSON.stringify(tags) }); - console.log(JSON.stringify(jsonData)); return api({ url: `${base_url}/api/discourse_post/`, method: METHOD.POST, @@ -66,7 +65,6 @@ export const createTagPost = (tag, targetName, msg) => { post_title: tag.tag, post_content: msg }); - console.log(JSON.stringify(jsonData)); return api({ url: `${base_url}/api/discourse_post/`, method: METHOD.POST, @@ -78,7 +76,6 @@ export const getExistingPost = projectName => { let jsonData = getDiscourseRequestObject({ post_title: projectName }); - console.log(JSON.stringify(jsonData)); return api({ url: `${base_url}/api/discourse_post/`, method: METHOD.POST, @@ -100,5 +97,3 @@ export const openDiscourseLink = url => { window.open(`${DJANGO_CONTEXT.discourse_host}${url}`, '_blank'); } }; - -console.log(DJANGO_CONTEXT.discourse_host); diff --git a/package.json b/package.json index 9d30bc52a..1e4a0af42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fragalysis-frontend", "version": "0.10.161", - "default_squonk_project": "project-d89f85d2-cec1-4449-9435-6323bb5c34e0", + "default_squonk_project": "project-8f32b412-8329-4469-a39c-8581efa93796", "description": "Frontend for fragalysis", "main": "webpack.config.js", "scripts": {