Skip to content

Commit

Permalink
- #1419 first batch of fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
boriskovar-m2ms committed Jul 24, 2024
1 parent 19317ee commit d60bdc0
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 9 deletions.
1 change: 0 additions & 1 deletion js/components/preview/compounds/redux/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,3 @@ export const compoundsColors = {
};

export const AUX_VECTOR_SELECTOR_DATASET_ID = 'vector_selector';

1 change: 1 addition & 0 deletions js/components/preview/molecule/moleculeList.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import GroupNglControlButtonsContext from './groupNglControlButtonsContext';
import { extractTargetFromURLParam } from '../utils';
import { LoadingContext } from '../../loading';
import { DJANGO_CONTEXT } from '../../../utils/djangoContext';
import { useScrollToSelectedPose } from './useScrollToSelectedPose';

const useStyles = makeStyles(theme => ({
container: {
Expand Down
7 changes: 7 additions & 0 deletions js/components/preview/molecule/observationCmpList.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { LoadingContext } from '../../loading';
import { DJANGO_CONTEXT } from '../../../utils/djangoContext';
import ObservationCmpView from './observationCmpView';
import { ObservationsDialog } from './observationsDialog';
import { useScrollToSelectedPose } from './useScrollToSelectedPose';

const useStyles = makeStyles(theme => ({
container: {
Expand Down Expand Up @@ -334,6 +335,11 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
target = directDisplay.target;
}

const { addMoleculeViewRef, setScrollToMoleculeId, getNode } = useScrollToSelectedPose(
moleculesPerPage,
setCurrentPage
);

let selectedMolecule = [];
// TODO: Reset Infinity scroll
/*useEffect(() => {
Expand Down Expand Up @@ -1221,6 +1227,7 @@ export const ObservationCmpList = memo(({ hideProjects }) => {

return (
<ObservationCmpView
ref={addMoleculeViewRef}
key={data.id}
imageHeight={imgHeight}
imageWidth={imgWidth}
Expand Down
142 changes: 142 additions & 0 deletions js/components/preview/molecule/useScrollToSelectedPose.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { setScrollFiredForLHS } from '../../../reducers/selection/actions';
import { getLHSCompoundsList } from './redux/selectors';

/**
* A hook which scrolls to the first selected pose when a snapshot is loaded.
*/
export const useScrollToSelectedPose = (moleculesPerPage, setCurrentPage) => {
const dispatch = useDispatch();

const poses = useSelector(state => getLHSCompoundsList(state), shallowEqual);
const ligands = useSelector(state => state.selectionReducers.fragmentDisplayList);
const proteins = useSelector(state => state.selectionReducers.proteinList);
const complexes = useSelector(state => state.selectionReducers.complexList);
const surfaces = useSelector(state => state.selectionReducers.surfaceList);
const densityList = useSelector(state => state.selectionReducers.densityList);
const densityListCustom = useSelector(state => state.selectionReducers.densityListCustom);
const vectorOnList = useSelector(state => state.selectionReducers.vectorOnList);

const scrollFired = useSelector(state => state.selectionReducers.isScrollFiredForLHS);

const [moleculeViewRefs, setMoleculeViewRefs] = useState({});
const [scrollToMoleculeId, setScrollToMoleculeId] = useState(null);

// First pass, iterates over all the molecules and checks if any of them is selected. If it is,
// it saves the ID of the molecule and determines how many pages of molecules should be displayed.
// This is done only once and only if right hand side is open.
// This also gets reset on snapshot change.
useEffect(() => {
if (!scrollFired) {
if (
ligands?.length ||
proteins?.length ||
complexes?.length ||
surfaces?.length ||
densityList?.length ||
densityListCustom?.length ||
vectorOnList?.length
) {
for (let i = 0; i < poses.length; i++) {
const pose = poses[i];
const molsForCmp = pose.associatedObs;

if (
containsAtLeastOne(ligands, molsForCmp) ||
containsAtLeastOne(proteins, molsForCmp) ||
containsAtLeastOne(complexes, molsForCmp) ||
containsAtLeastOne(surfaces, molsForCmp) ||
containsAtLeastOne(densityList, molsForCmp) ||
containsAtLeastOne(densityListCustom, molsForCmp) ||
containsAtLeastOne(vectorOnList, molsForCmp)
) {
setCurrentPage(i / moleculesPerPage + 1);
setScrollToMoleculeId(pose.id);
break;
}
}
}
}

dispatch(setScrollFiredForLHS(true));
}, [
dispatch,
poses,
moleculesPerPage,
scrollFired,
setCurrentPage,
ligands,
proteins,
complexes,
surfaces,
densityList.length,
densityListCustom.length,
densityList,
densityListCustom,
vectorOnList
]);

// Second pass, once the list of molecules is displayed and the refs to their DOM nodes have been
// obtained, scroll to the the saved molecule from the first pass.
// setTimeout might be necessary for the scrolling to happen.
useEffect(() => {
if (scrollToMoleculeId !== null) {
const node = moleculeViewRefs[scrollToMoleculeId];
if (node) {
setScrollToMoleculeId(null);
if (!elementIsVisibleInViewport(node)) {
setTimeout(() => {
node.scrollIntoView();
});
}
}
}
}, [moleculeViewRefs, scrollToMoleculeId]);

const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
return partiallyVisible
? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
: top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};

// Used to attach the ref of DOM nodes.
// const addMoleculeViewRef = useCallback((moleculeId, node) => {
// setMoleculeViewRefs(prevRefs => ({
// ...prevRefs,
// [moleculeId]: node
// }));
// }, []);

const addMoleculeViewRef = useCallback((moleculeId, node) => {
setMoleculeViewRefs(prevRefs => {
if (prevRefs.hasOwnProperty(moleculeId)) return prevRefs;
return {
...prevRefs,
[moleculeId]: node
};
});
}, []);

const getNode = useCallback(
molId => {
return moleculeViewRefs[molId];
},
[moleculeViewRefs]
);

const containsAtLeastOne = (list, molsList) => {
for (const mol in molsList) {
if (list.includes(mol.id)) {
return true;
}
}

return false;
};

return { addMoleculeViewRef, setScrollToMoleculeId, getNode };
};
7 changes: 7 additions & 0 deletions js/reducers/selection/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,10 @@ export const addToastMessage = toastMessage => {
toastMessage: toastMessage
};
};

export const setScrollFiredForLHS = isFired => {
return {
type: constants.SET_SCROLL_FIRED_FOR_LHS,
isFired: isFired
};
};
3 changes: 2 additions & 1 deletion js/reducers/selection/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export const constants = {
SET_LHS_COMPOUNDS_INITIALIZED: prefix + 'SET_LHS_COMPOUNDS_INITIALIZED',

ADD_TOAST_MESSAGE: prefix + 'ADD_TOAST_MESSAGE',
SET_TOAST_MESSAGES: prefix + 'SET_TOAST_MESSAGES'
SET_TOAST_MESSAGES: prefix + 'SET_TOAST_MESSAGES',
SET_SCROLL_FIRED_FOR_LHS: prefix + 'SET_SCROLL_FIRED_FOR_LHS'
};

export const PREDEFINED_FILTERS = {
Expand Down
6 changes: 5 additions & 1 deletion js/reducers/selection/selectionReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ export const INITIAL_STATE = {
poseIdForObservationsDialog: 0,

areLSHCompoundsInitialized: false,
toastMessages: []
toastMessages: [],
isScrollFiredForLHS: false
};

export function selectionReducers(state = INITIAL_STATE, action = {}) {
switch (action.type) {
case constants.SET_SCROLL_FIRED_FOR_LHS: {
return { ...state, isScrollFiredForLHS: action.isFired };
}
case constants.SET_TO_BUY_LIST:
return Object.assign({}, state, {
to_buy_list: action.to_buy_list
Expand Down
2 changes: 2 additions & 0 deletions js/reducers/tracking/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const actionType = {
QUALITY_TURNED_OFF: 'QUALITY_TURNED_OFF',
VECTORS_TURNED_ON: 'VECTORS_TURNED_ON',
VECTORS_TURNED_OFF: 'VECTORS_TURNED_OFF',
COLOR_FILTER_TURNED_ON: 'COLOR_FILTER_TURNED_ON',
COLOR_FILTER_TURNED_OFF: 'COLOR_FILTER_TURNED_OFF',
CLASS_SELECTED: 'CLASS_SELECTED',
CLASS_UPDATED: 'CLASS_UPDATED',
VECTOR_SELECTED: 'VECTOR_SELECTED',
Expand Down
62 changes: 57 additions & 5 deletions js/reducers/tracking/dispatchActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
removeFromMolListToEdit,
setNextXMolecules,
setTagDetailView,
addToastMessage
addToastMessage,
setScrollFiredForLHS
} from '../selection/actions';
import {
resetReducersForRestoringActions,
Expand Down Expand Up @@ -96,7 +97,9 @@ import {
appendCompoundColorOfDataset,
removeCompoundColorOfDataset,
setCompoundToSelectedCompoundsByDataset,
setSelectAllButtonForDataset
setSelectAllButtonForDataset,
appendColorToSelectedColorFilter,
removeColorFromSelectedColorFilter
} from '../../components/datasets/redux/actions';
import {
removeComponentRepresentation,
Expand Down Expand Up @@ -482,7 +485,7 @@ const saveActionsList = (project, snapshot, actionList, nglViewList, isAnonymous
getCommonLastActionByType(orderedActionList, actionType.DATASET_INDEX, currentActions);
getCommonLastActionByType(orderedActionList, actionType.DATASET_FILTER, currentActions);
getCommonLastActionByType(orderedActionList, actionType.DATASET_FILTER_SCORE, currentActions);
getCommonLastActionByType(orderedActionList, actionType.CLASS_SELECTED, currentActions);
getLastColorFilterActions(orderedActionList, actionType.COLOR_FILTER_TURNED_OFF, currentActions);
getCommonLastActionByType(orderedActionList, actionType.CLASS_UPDATED, currentActions);
getCommonLastActionByType(orderedActionList, actionType.ISO_LEVEL_EVENT, currentActions);
getCommonLastActionByType(orderedActionList, actionType.BOX_SIZE_EVENT, currentActions);
Expand Down Expand Up @@ -660,6 +663,16 @@ const getCurrentActionListDensity = (orderedActionList, type, collection, curren
}
};

const getLastColorFilterActions = (orderedActionList, type, currentActions) => {
let actionList = orderedActionList.filter(action => action.type === type);
Object.keys(compoundsColors).forEach(color => {
let action = actionList.find(action => action.value === color);
if (action) {
currentActions.push({ ...action });
}
});
};

const getCommonLastActionByType = (orderedActionList, type, currentActions) => {
let action = orderedActionList.find(action => action.type === type);
if (action) {
Expand Down Expand Up @@ -1037,6 +1050,7 @@ export const restoreAfterTargetActions = (stages, projectId, snapshotId) => asyn
dispatch(setIsActionsRestoring(false, true));
dispatch(restoreViewerControlActions(orderedActionList));
dispatch(resetDatasetScrolledMap()); // Have a look at useScrollToSelected.js
dispatch(setScrollFiredForLHS(false));
dispatch(setIsSnapshotDirty(false));
dispatch(restoreSearchString(orderedActionList));
dispatch(restoreSearchStringHitNavigator(orderedActionList));
Expand Down Expand Up @@ -1458,7 +1472,12 @@ export const restoreSitesActions = orderedActionList => (dispatch, getState) =>
if (tag) {
dispatch(addSelectedTag(tag));
if (tag.hidden) {
dispatch(addToastMessage({ level: TOAST_LEVELS.WARNING, text: `Tag ${tag.tag} is selected in the snapshot but it's hidden.` }));
dispatch(
addToastMessage({
level: TOAST_LEVELS.WARNING,
text: `Tag ${tag.tag} is selected in the snapshot but it's hidden.`
})
);
}
}
});
Expand All @@ -1476,7 +1495,12 @@ export const restoreTagActions = orderedActionList => (dispatch, getState) => {
if (tag) {
dispatch(addSelectedTag(tag));
if (tag.hidden) {
dispatch(addToastMessage({ level: TOAST_LEVELS.WARNING, text: `Tag ${tag.tag} is selected in the snapshot but it's hidden.` }));
dispatch(
addToastMessage({
level: TOAST_LEVELS.WARNING,
text: `Tag ${tag.tag} is selected in the snapshot but it's hidden.`
})
);
}
}
});
Expand Down Expand Up @@ -1561,6 +1585,11 @@ export const restoreCartActions = (orderedActionList, majorViewStage) => async (
dispatch(setCompoundClasses(newValue, oldValue, value, id));
}

let colorFilterRemoveActions = orderedActionList.filter(action => action.type === actionType.COLOR_FILTER_TURNED_OFF);
colorFilterRemoveActions?.forEach(action => {
dispatch(handleColorFilterAction(action, false));
});

let vectorCompoundActions = orderedActionList.filter(action => action.type === actionType.VECTOR_COUMPOUND_ADDED);
if (vectorCompoundActions) {
vectorCompoundActions.forEach(action => {
Expand Down Expand Up @@ -2368,6 +2397,12 @@ const handleUndoAction = (action, stages) => (dispatch, getState) => {
case actionType.CLASS_UPDATED:
dispatch(handleClassUpdatedAction(action, false));
break;
case actionType.COLOR_FILTER_TURNED_ON:
dispatch(handleColorFilterAction(action, false));
break;
case actionType.COLOR_FILTER_TURNED_OFF:
dispatch(handleColorFilterAction(action, true));
break;
case actionType.TARGET_LOADED:
dispatch(handleTargetAction(action, false));
break;
Expand Down Expand Up @@ -2643,6 +2678,12 @@ const handleRedoAction = (action, stages) => (dispatch, getState) => {
case actionType.CLASS_UPDATED:
dispatch(handleClassUpdatedAction(action, true));
break;
case actionType.COLOR_FILTER_TURNED_ON:
dispatch(handleColorFilterAction(action, true));
break;
case actionType.COLOR_FILTER_TURNED_OFF:
dispatch(handleColorFilterAction(action, false));
break;
case actionType.TARGET_LOADED:
dispatch(handleTargetAction(action, true));
break;
Expand Down Expand Up @@ -3047,6 +3088,17 @@ const handleClassUpdatedAction = (action, isAdd) => (dispatch, getState) => {
}
};

const handleColorFilterAction = (action, isSelected) => (dispatch, getState) => {
if (action) {
const color = action.value;
if (isSelected) {
dispatch(appendColorToSelectedColorFilter(color));
} else {
dispatch(removeColorFromSelectedColorFilter(color));
}
}
};

const handleTargetAction = (action, isSelected, stages) => (dispatch, getState) => {
const state = getState();
if (action) {
Expand Down
3 changes: 2 additions & 1 deletion js/reducers/tracking/dispatchActionsSwitchSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
setIsSnapshotDirty,
setSnapshotActionsDownloaded
} from './actions';
import { resetSelectionState } from '../selection/actions';
import { resetSelectionState, setScrollFiredForLHS } from '../selection/actions';
import { resetDatasetsStateOnSnapshotChange, resetDatasetScrolledMap } from '../../components/datasets/redux/actions';
import { resetViewerControlsState } from '../../components/preview/viewerControls/redux/actions';
import { resetNglTrackingState } from '../nglTracking/dispatchActions';
Expand Down Expand Up @@ -132,6 +132,7 @@ export const restoreAfterSnapshotChange = (stages, projectId) => async (dispatch
// console.count(`AFTER restoration orientation from snapshot`);

dispatch(resetDatasetScrolledMap()); // Have a look at useScrollToSelected.js
dispatch(setScrollFiredForLHS(false));
dispatch(setIsActionsRestoring(false, true));

console.count(`restoreAfterSnapshotChange end`);
Expand Down
Loading

0 comments on commit d60bdc0

Please sign in to comment.