Skip to content

Commit

Permalink
Merge pull request #475 from xchem/staging
Browse files Browse the repository at this point in the history
Push to production
mwinokan authored Jan 28, 2025
2 parents 33f9bf7 + 9e58751 commit 6681d51
Showing 17 changed files with 542 additions and 1,455 deletions.
27 changes: 23 additions & 4 deletions js/components/common/Components/SearchField/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { makeStyles, TextField, InputAdornment } from '@material-ui/core';
import { makeStyles, TextField, InputAdornment, IconButton } from '@material-ui/core';
import { Search } from '@material-ui/icons';
import classNames from 'classnames';
import { debounce } from 'lodash';
@@ -31,9 +31,18 @@ const useStyles = makeStyles(theme => ({
}
}));

const SearchField = ({ className, id, placeholder, size, onChange, disabled, searchString }) => {
const SearchField = ({
className,
id,
placeholder,
size,
onChange,
disabled,
searchString,
searchIconAction = null
}) => {
const classes = useStyles();
let value = searchString ?? '';
let value = searchString ?? '';

const debounced = useMemo(
() =>
@@ -57,7 +66,17 @@ const SearchField = ({ className, id, placeholder, size, onChange, disabled, sea
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search color="inherit" />
{searchIconAction ? (
<IconButton
color="inherit"
sx={{ pointerEvents: 'auto', cursor: 'pointer' }}
onClick={() => searchIconAction(true)}
>
<Search color="inherit" />
</IconButton>
) : (
<Search color="inherit" />
)}
</InputAdornment>
),
className: classes.input
3 changes: 2 additions & 1 deletion js/components/direct/constants.js
Original file line number Diff line number Diff line change
@@ -3,5 +3,6 @@ export const URL_TOKENS = {
molecules: 'mols',
exact: 'exact',
tag: 'tag',
target_access_string: 'tas'
target_access_string: 'tas',
compound: 'compound'
};
20 changes: 16 additions & 4 deletions js/components/direct/directDisplay.js
Original file line number Diff line number Diff line change
@@ -34,7 +34,11 @@ export const DirectDisplay = memo(props => {
useEffect(() => {
// example url http://127.0.0.1:8080/viewer/react/preview/direct/target/MID2A/tas/lb00000/mols/X0301_0A/L/P/S/X0393_0B/L/P
// example url with 'exact' https://fragalysis-tibor-default.xchem-dev.diamond.ac.uk/viewer/react/preview/direct/target/NUDT7A_CRUDE/mols/NUDT7A_CRUDE-X0156_1/exact/L/P
// based on the issues #431, #448, #447
// in two cases above we are searching for molecules using shortcode
// Now the search term is looked up in the `shortcode`, `compound ID` and all of the `aliases` (I can change this pretty quickly)
// `http://127.0.0.1:8080/viewer/react/preview/direct/target/A71EV2A/tas/lb18145-1/compound/7516/L/S/nonsense-45/L/P/exact/Z4/L/C/A0853a/L/P`
// URL above shows `L` and `S` for observation which contains substring `7516`, `L` and `P` for observation which exactly has string `nonsense-45` as a shortcode,
// compound ID or one of the aliases, `L` and `C` for all observations which contain substring `Z4`, and `L` and `P` for observations which contains substring `A0853a`
const param = match.params[0];
if (!directAccessProcessed && param && param.startsWith(URL_TOKENS.target)) {
let withoutKeyword = param.split(URL_TOKENS.target);
@@ -63,7 +67,13 @@ export const DirectDisplay = memo(props => {
}
}
}
if (rest && rest.length > 1 && rest[0] === URL_TOKENS.molecules) {
if (rest && rest.length > 1 && (rest[0] === URL_TOKENS.molecules || rest[0] === URL_TOKENS.compound)) {
let searchSettings = { searchBy: {} };
if (rest[0] === URL_TOKENS.molecules) {
searchSettings = { searchBy: { shortcode: true, aliases: false, compoundId: false } };
} else if (rest[0] === URL_TOKENS.compound) {
searchSettings = { searchBy: { shortcode: true, aliases: true, compoundId: true } };
}
rest = rest.slice(1);
let i;
let currentMolecule;
@@ -105,7 +115,8 @@ export const DirectDisplay = memo(props => {
C: false,
S: false,
V: false,
exact: false
exact: false,
searchSettings: searchSettings
};
molecules.push(currentMolecule);
}
@@ -119,7 +130,8 @@ export const DirectDisplay = memo(props => {
C: false,
S: false,
V: false,
exact: false
exact: false,
searchSettings: searchSettings
};
molecules.push(currentMolecule);
}
2 changes: 1 addition & 1 deletion js/components/preview/Preview.js
Original file line number Diff line number Diff line change
@@ -354,5 +354,5 @@ const Preview = memo(({ isStateLoaded, hideProjects, isSnapshot = false }) => {
});

export default withLoadingJobSpecs(
withSnapshotManagement(withUpdatingTarget(withLoadingProtein(withLoadingProjects(Preview))))
withLoadingProjects(withSnapshotManagement(withUpdatingTarget(withLoadingProtein(Preview))))
);
1 change: 0 additions & 1 deletion js/components/preview/molecule/hitNavigator.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
* Created by abradley on 14/03/2018.
*/
import React, { memo } from 'react';
import { MoleculeList } from './moleculeList';
import { ObservationCmpList } from './observationCmpList';

const HitNavigator = memo(({ hideProjects }) => {
1,291 changes: 0 additions & 1,291 deletions js/components/preview/molecule/moleculeList.js

This file was deleted.

28 changes: 18 additions & 10 deletions js/components/preview/molecule/observationCmpList.js
Original file line number Diff line number Diff line change
@@ -49,7 +49,8 @@ import {
removeSelectedTypesInHitNavigator,
selectAllHits,
autoHideTagEditorDialogsOnScroll,
selectAllVisibleObservations
selectAllVisibleObservations,
searchForObservations
} from './redux/dispatchActions';
import { DEFAULT_FILTER, PREDEFINED_FILTERS } from '../../../reducers/selection/constants';
import { Edit, FilterList } from '@material-ui/icons';
@@ -64,7 +65,8 @@ import {
setOpenObservationsDialog,
setLHSCompoundsInitialized,
setPoseIdForObservationsDialog,
setObservationDialogAction
setObservationDialogAction,
setSearchSettingsDialogOpen
} from '../../../reducers/selection/actions';
import { initializeFilter } from '../../../reducers/selection/dispatchActions';
import * as listType from '../../../constants/listTypes';
@@ -88,6 +90,7 @@ import { DJANGO_CONTEXT } from '../../../utils/djangoContext';
import ObservationCmpView from './observationCmpView';
import { ObservationsDialog } from './observationsDialog';
import { useScrollToSelectedPose } from './useScrollToSelectedPose';
import { SearchSettingsDialog } from './searchSettingsDialog';

const useStyles = makeStyles(theme => ({
container: {
@@ -277,7 +280,6 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
const moleculesPerPage = 30;
const [currentPage, setCurrentPage] = useState(0);
const searchString = useSelector(state => state.previewReducers.molecule.searchStringLHS);
// const [searchString, setSearchString] = useState(null);
const [sortDialogAnchorEl, setSortDialogAnchorEl] = useState(null);
const oldUrl = useRef('');
const setOldUrl = url => {
@@ -327,6 +329,9 @@ export const ObservationCmpList = memo(({ hideProjects }) => {

const proteinsHasLoaded = useSelector(state => state.nglReducers.proteinsHasLoaded);

const searchSettingsDialogOpen = useSelector(state => state.selectionReducers.searchSettingsDialogOpen);
const searchSettings = useSelector(state => state.selectionReducers.searchSettings);

const [predefinedFilter, setPredefinedFilter] = useState(filter !== undefined ? filter.predefined : DEFAULT_FILTER);

const [ascending, setAscending] = useState(true);
@@ -467,17 +472,12 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
}, [object_selection]);*/

let joinedMoleculeLists = useMemo(() => {
// const searchedString = currentActionList.find(action => action.type === 'SEARCH_STRING_HIT_NAVIGATOR');
if (searchString) {
return allMoleculesList.filter(molecule => molecule.code.toLowerCase().includes(searchString.toLowerCase()));
// } else if (searchedString) {
// return getJoinedMoleculeList.filter(molecule =>
// molecule.protein_code.toLowerCase().includes(searchedString.searchStringHitNavigator.toLowerCase())
// );
return dispatch(searchForObservations(searchString, allMoleculesList, searchSettings));
} else {
return getJoinedMoleculeList;
}
}, [getJoinedMoleculeList, allMoleculesList, searchString]);
}, [searchString, dispatch, allMoleculesList, getJoinedMoleculeList, searchSettings]);

const addSelectedMoleculesFromUnselectedSites = useCallback(
(joinedMoleculeLists, list) => {
@@ -1014,6 +1014,10 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
return molecules;
};

const openSearchSettingsDialog = open => {
dispatch(setSearchSettingsDialogOpen(open));
};

const actions = [
<SearchField
className={classes.search}
@@ -1026,6 +1030,7 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
// searchString={filterSearchString?.searchStringHitNavigator ?? ''}
searchString={searchString ?? ''}
placeholder="Search"
searchIconAction={openSearchSettingsDialog}
/>,

<IconButton
@@ -1118,6 +1123,9 @@ export const ObservationCmpList = memo(({ hideProjects }) => {
setIsOpenLPCAlert(false);
}}
/>
{searchSettingsDialogOpen && (
<SearchSettingsDialog openDialog={searchSettingsDialogOpen} setOpenDialog={openSearchSettingsDialog} />
)}
{isObservationDialogOpen && (
<ObservationsDialog open={isObservationDialogOpen} anchorEl={tagEditorAnchorEl} ref={tagEditorRef} />
)}
158 changes: 118 additions & 40 deletions js/components/preview/molecule/observationCmpView/observationCmpView.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

import React, { memo, useEffect, useState, useRef, useContext, useCallback, forwardRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Button, Grid, makeStyles, Tooltip, IconButton, Popper, CircularProgress } from '@material-ui/core';
import { Button, Grid, makeStyles, Tooltip, IconButton, Popper, CircularProgress, Table, TableBody, TableRow, TableCell } from '@material-ui/core';
import { Panel } from '../../../common';
import { MyLocation, Warning, Assignment, AssignmentTurnedIn } from '@material-ui/icons';
import SVGInline from 'react-svg-inline';
@@ -63,6 +63,7 @@ import { getFontColorByBackgroundColor } from '../../../../utils/colors';
import MoleculeSelectCheckbox from '../moleculeView/moleculeSelectCheckbox';
import { isAnyObservationTurnedOnForCmp } from '../../../../reducers/selection/selectors';
import { first } from 'lodash';
import { ToastContext } from '../../../toast';

const useStyles = makeStyles(theme => ({
container: {
@@ -381,9 +382,26 @@ const useStyles = makeStyles(theme => ({
marginRight: 5,
position: 'right'
},
tooltipRow: {
marginTop: 2,
marginBottom: 2
posePropertiesTableCell: {
padding: '4px 8px'
},
posePropertiesTable: {
pointerEvents: 'auto',
'& tr > td:nth-of-type(2)': {
border: 'none',
borderLeft: '1px dashed ' + theme.palette.primary.main
},
'& tr:hover': {
backgroundColor: theme.palette.primary.light
}
},
posePropertiesTableIcon: {
padding: 0,
color: theme.palette.grey[500]
},
posePropertiesTableIconActive: {
padding: 0,
color: theme.palette.grey[700]
}
}));

@@ -441,6 +459,7 @@ const ObservationCmpView = memo(

const [hasMap, setHasMap] = useState(false);

const { toastInfo } = useContext(ToastContext);
const { getNglView } = useContext(NglContext);
const stage = getNglView(VIEWS.MAJOR_VIEW) && getNglView(VIEWS.MAJOR_VIEW).stage;

@@ -1316,26 +1335,62 @@ const ObservationCmpView = memo(
return displayName;
}, [aliasOrder, getMainObservation]);

const getDisplayNameTooltip = useCallback(() => {
const mainObservation = getMainObservation();
const tooltip = <>
<p className={classes.tooltipRow}>{mainObservation?.prefix_tooltip ?? '-'}</p>
{aliasOrder?.map((alias, index) => {
if (alias === 'compound_code') {
return <p key={index} className={classes.tooltipRow}>{`${alias}: ${mainObservation?.compound_code}`}</p>;
// return <><br></br>{`${alias}: ${mainObservation?.compound_code}`}</>;
} else {
const searchedIdentifier = mainObservation.identifiers.find(identifier => identifier.type === alias);
if (searchedIdentifier) {
return <p key={index} className={classes.tooltipRow}>{`${alias}: ${searchedIdentifier.name}`}</p>;
// return <><br></br>{`${alias}: ${searchedIdentifier.name}`}</>;
}
}
})}
</>;
const copyToClipboard = useCallback(async (type, text) => {
await navigator.clipboard.writeText(text);
toastInfo(`${text} of '${type}' was copied to the clipboard`, { autoHideDuration: 5000 });
}, [toastInfo]);

return tooltip;
}, [aliasOrder, getMainObservation, classes.tooltipRow]);
const [anchorElTable, setAnchorElTable] = useState(null);
const [tableIsOpen, setTableIsOpen] = useState(false);
const handleTablePopoverOpen = (event) => {
setAnchorElTable(event.currentTarget);
};
const handleTablePopoverClose = () => {
setAnchorElTable(null);
setTableIsOpen(false);
};
const popoverOpen = Boolean(anchorElTable) || tableIsOpen;

const getPosePropertiesTable = useCallback(() => {
const mainObservation = getMainObservation();
const observationCode = getMainObservation()?.code.replaceAll(`${target_on_name}-`, '');

return <Table className={classes.posePropertiesTable}
onMouseLeave={() => setTableIsOpen(false)}
onMouseEnter={() => setTableIsOpen(true)}>
<TableBody>
<Tooltip title={'Click to copy value of smiles'}>
<TableRow onClick={() => copyToClipboard('smiles', data.smiles)}>
<TableCell className={classes.posePropertiesTableCell}>copy smiles</TableCell>
<TableCell className={classes.posePropertiesTableCell}>{data.smiles}</TableCell>
</TableRow>
</Tooltip>
<Tooltip title={'Click to copy value of observation code'}>
<TableRow onClick={() => copyToClipboard('smiles', observationCode)}>
<TableCell className={classes.posePropertiesTableCell}>copy observation code</TableCell>
<TableCell className={classes.posePropertiesTableCell}>{observationCode}</TableCell>
</TableRow>
</Tooltip>
<Tooltip title={'Click to copy value of prefix_tooltip'}>
<TableRow onClick={() => copyToClipboard('prefix_tooltip', mainObservation?.prefix_tooltip)}>
<TableCell className={classes.posePropertiesTableCell}>copy prefix_tooltip</TableCell>
<TableCell className={classes.posePropertiesTableCell}>{mainObservation?.prefix_tooltip ?? ''}</TableCell>
</TableRow>
</Tooltip>
{aliasOrder?.map((alias, index) => {
const compoundCode = mainObservation.identifiers.find(identifier => identifier.type === alias)?.name ?? '';
return <Tooltip key={index} title={`Click to copy value of ${alias}`}>
<TableRow onClick={() => copyToClipboard(alias, alias === 'compound_code' ? mainObservation?.compound_code : compoundCode)}>
<TableCell className={classes.posePropertiesTableCell}>{`copy ${alias}`}</TableCell>
{(alias === 'compound_code') ?
<TableCell className={classes.posePropertiesTableCell}>{`${mainObservation?.compound_code}`}</TableCell>
: <TableCell className={classes.posePropertiesTableCell}>{`${compoundCode}`}</TableCell>}
</TableRow>
</Tooltip>;
})}
</TableBody>
</Table>;
}, [aliasOrder, copyToClipboard, data.smiles, getMainObservation, classes.posePropertiesTable, classes.posePropertiesTableCell, target_on_name]);

return (
<>
@@ -1387,22 +1442,45 @@ const ObservationCmpView = memo(
</Grid>
<Grid item container className={classes.detailsCol} justifyContent="space-evenly" direction="column" xs={2}>
{/* Title label */}
<Tooltip title={getDisplayNameTooltip()} placement="bottom-start">
<Grid
item
onCopy={e => {
e.preventDefault();
setNameCopied(moleculeTitle);
}}
className={classes.moleculeTitleLabel}
<Grid
item
onCopy={e => {
e.preventDefault();
setNameCopied(moleculeTitle);
}}
className={classes.moleculeTitleLabel}
>
<span className={classes.moleculeTitleLabelMain}>
{getMainObservation()?.code.replaceAll(`${target_on_name}-`, '')}
</span>
<br />
{getDisplayName()}
<IconButton className={popoverOpen ? classes.posePropertiesTableIconActive : classes.posePropertiesTableIcon}
onMouseEnter={handleTablePopoverOpen}
onMouseLeave={() => setAnchorElTable(null)}
ref={anchorElTable}
>
<span className={classes.moleculeTitleLabelMain}>
{getMainObservation()?.code.replaceAll(`${target_on_name}-`, '')}
</span>
<br />
{getDisplayName()}
</Grid>
</Tooltip>
<Assignment />
<Popover
id="mouse-over-popover"
style={{ pointerEvents: 'none' }}
open={popoverOpen}
anchorEl={anchorElTable}
anchorOrigin={{
vertical: 'center',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'center',
horizontal: 'left'
}}
onClose={handleTablePopoverClose}
disableRestoreFocus
>
{getPosePropertiesTable()}
</Popover>
</IconButton>
</Grid>
{/* "Filtered"/calculated props
<Grid item>
<Grid
@@ -1650,13 +1728,13 @@ const ObservationCmpView = memo(
>
{svg_image}
<div className={classes.imageActions}>
{moleculeTooltipOpen && (
{/* {moleculeTooltipOpen && (
<Tooltip title={!isCopied ? 'Copy smiles' : 'Copied'}>
<IconButton className={classes.copyIcon} onClick={setCopied}>
{!isCopied ? <Assignment /> : <AssignmentTurnedIn />}
</IconButton>
</Tooltip>
)}
)} */}
{warningIconVisible && (
<Tooltip title="Warning">
<IconButton className={classes.warningIcon} onClick={() => onQuality()}>
157 changes: 108 additions & 49 deletions js/components/preview/molecule/observationsDialog.js
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import {
removeObservationsFromPose,
removeSelectedMolTypes,
removeSurface,
searchForObservations,
updateObservationsInPose,
updatePose,
withDisabledMoleculesNglControlButtons
@@ -49,9 +50,19 @@ import MoleculeView from './moleculeView';
import { TagEditor } from '../tags/modal/tagEditor';
import { ToastContext } from '../../toast';
import { DJANGO_CONTEXT } from '../../../utils/djangoContext';
import { updateLHSCompound, updateMoleculeInMolLists, updateMoleculeTag, updateTag } from '../../../reducers/api/actions';
import {
updateLHSCompound,
updateMoleculeInMolLists,
updateMoleculeTag,
updateTag
} from '../../../reducers/api/actions';
import { createPoseErrorMessage } from './api/poseApi';
import { augumentTagObjectWithId, createMoleculeTagObject, DEFAULT_TAG_COLOR, getMoleculeTagForTag } from '../tags/utils/tagUtils';
import {
augumentTagObjectWithId,
createMoleculeTagObject,
DEFAULT_TAG_COLOR,
getMoleculeTagForTag
} from '../tags/utils/tagUtils';
import { updateExistingTag } from '../tags/api/tagsApi';
import { XCA_TAGS_CATEGORIES } from './moleculeView/moleculeView';

@@ -251,6 +262,8 @@ export const ObservationsDialog = memo(
const tagList = useSelector(state => state.apiReducers.moleculeTags);
const targetId = useSelector(state => state.apiReducers.target_on);

const searchSettings = useSelector(state => state.selectionReducers.searchSettings);

const poses = useSelector(state => state.apiReducers.lhs_compounds_list);
const compatiblePoses = useMemo(() => {
const someObservation = observationsDataList[0];
@@ -270,9 +283,9 @@ export const ObservationsDialog = memo(
const tagEditorRef = useRef();

const getCalculatedTagColumnWidth = (tagText, font = null) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.font = `${(font ?? '12px')} "Roboto", "Helvetica", "Arial", sans-serif`;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = `${font ?? '12px'} "Roboto", "Helvetica", "Arial", sans-serif`;
// 16 as padding buffer
const calculatedWidth = ctx.measureText(tagText).width + 16;
return calculatedWidth;
@@ -302,19 +315,20 @@ export const ObservationsDialog = memo(
setHeaderWidths(old => {
const newWidths = { ...old };
newWidths[tagCategory] = calculatedWidth;
return { ...newWidths }
return { ...newWidths };
});
}
};

const moleculeList = useMemo(() => {
if (searchString !== null) {
return observationsDataList.filter(molecule =>
molecule.code.toLowerCase().includes(searchString.toLowerCase())
);
// return observationsDataList.filter(molecule =>
// molecule.code.toLowerCase().includes(searchString.toLowerCase())
// );
return dispatch(searchForObservations(searchString, observationsDataList, searchSettings));
}
return observationsDataList;
}, [observationsDataList, searchString]);
}, [dispatch, observationsDataList, searchSettings, searchString]);

const allSelectedMolecules = useMemo(
() => observationsDataList.filter(molecule => moleculesToEditIds.includes(molecule.id)),
@@ -630,7 +644,9 @@ export const ObservationsDialog = memo(
const totalApproximateHeight = observationsApproximateHeight + headerFooterApproximateHeight;
/*if (totalApproximateHeight > maxHeight) {
height = maxHeight;
} else*/ if (totalApproximateHeight < MIN_PANEL_HEIGHT) {
} else*/ if (
totalApproximateHeight < MIN_PANEL_HEIGHT
) {
height = MIN_PANEL_HEIGHT;
} else {
height = totalApproximateHeight;
@@ -646,15 +662,20 @@ export const ObservationsDialog = memo(
* @param {string} category
* @return {array}
*/
const getTagsForCategory = useCallback(category => {
const tagCategory = tagCategoriesList.find(tagCategory => tagCategory.category === category);
return tagCategory ? tagList.filter(tag => {
if (tag.category === tagCategory.id) {
// console.log('good tag', { ...tag });
return true;
} else return false;
}) : [];
}, [tagCategoriesList, tagList]);
const getTagsForCategory = useCallback(
category => {
const tagCategory = tagCategoriesList.find(tagCategory => tagCategory.category === category);
return tagCategory
? tagList.filter(tag => {
if (tag.category === tagCategory.id) {
// console.log('good tag', { ...tag });
return true;
} else return false;
})
: [];
},
[tagCategoriesList, tagList]
);

const updateCmp = (cmp, obs) => {
let newCmp = { ...cmp };
@@ -849,7 +870,10 @@ export const ObservationsDialog = memo(
}
// then tag
await tagObservations(tag, mainObservationTag);
toastInfo(`Tag for observations was changed from "${mainObservationTag.upload_name}" to "${tag.upload_name}". They could disappear based on your tag selection`, { autoHideDuration: 5000 });
toastInfo(
`Tag for observations was changed from "${mainObservationTag.upload_name}" to "${tag.upload_name}". They could disappear based on your tag selection`,
{ autoHideDuration: 5000 }
);
};

/**
@@ -858,10 +882,17 @@ export const ObservationsDialog = memo(
* @param {string} category category of tag
* @returns {boolean}
*/
const disableXCATagChange = useCallback(category => {
// #1522 CanonSite tags should not be allowed to change if there are selected only some observations
return category === 'CanonSites' && allSelectedMolecules.length > 0 && (allSelectedMolecules.length !== moleculeList.length);
}, [allSelectedMolecules, moleculeList]);
const disableXCATagChange = useCallback(
category => {
// #1522 CanonSite tags should not be allowed to change if there are selected only some observations
return (
category === 'CanonSites' &&
allSelectedMolecules.length > 0 &&
allSelectedMolecules.length !== moleculeList.length
);
},
[allSelectedMolecules, moleculeList]
);

return (
<Popper id={id} open={open} anchorEl={anchorEl} placement="left-start" ref={ref}>
@@ -1006,27 +1037,40 @@ export const ObservationsDialog = memo(
</Button>
</Grid>
{expandView && (
<Grid
item
xs
container
alignItems="center"
style={{ marginLeft: 95 }}
>
{XCA_TAGS_CATEGORIES.map(
(tagCategory, index) => (
<Grid item align="center" key={index} className={classes.headerCell} style={{ minWidth: headerWidths[tagCategory] }}>
{PLURAL_TO_SINGULAR[tagCategory]}
</Grid>
)
)}
<Grid item align="center" className={classes.headerCell} style={{ minWidth: headerWidths.CentroidRes }}>
<Grid item xs container alignItems="center" style={{ marginLeft: 95 }}>
{XCA_TAGS_CATEGORIES.map((tagCategory, index) => (
<Grid
item
align="center"
key={index}
className={classes.headerCell}
style={{ minWidth: headerWidths[tagCategory] }}
>
{PLURAL_TO_SINGULAR[tagCategory]}
</Grid>
))}
<Grid
item
align="center"
className={classes.headerCell}
style={{ minWidth: headerWidths.CentroidRes }}
>
CentroidRes
</Grid>
<Grid item align="center" className={classes.headerCell} style={{ minWidth: headerWidths.LongCode }}>
<Grid
item
align="center"
className={classes.headerCell}
style={{ minWidth: headerWidths.LongCode }}
>
LongCode
</Grid>
<Grid item align="center" className={classes.headerCell} style={{ minWidth: headerWidths.Path }}>
<Grid
item
align="center"
className={classes.headerCell}
style={{ minWidth: headerWidths.Path }}
>
Path
</Grid>
</Grid>
@@ -1158,19 +1202,34 @@ export const ObservationsDialog = memo(
Change XCA tags
</Button>
<Grid container direction="row" className={classes.dropdownContent}>
{XCA_TAG_CATEGORIES.map(category =>
<Grid key={category} item className={classNames(classes.dropdown, classes.dropdownItem)} disabled={disableXCATagChange(category)}>
{XCA_TAG_CATEGORIES.map(category => (
<Grid
key={category}
item
className={classNames(classes.dropdown, classes.dropdownItem)}
disabled={disableXCATagChange(category)}
>
Change {PLURAL_TO_SINGULAR[category]}
{!disableXCATagChange(category) &&
<Grid container direction="row" className={classNames(classes.dropdownContent, classes.dropdownContentSide)}>
{!disableXCATagChange(category) && (
<Grid
container
direction="row"
className={classNames(classes.dropdownContent, classes.dropdownContentSide)}
>
{getTagsForCategory(category)?.map(tag => (
<Grid key={tag.id} item className={classes.dropdownItem} onClick={() => handleXCAtagChange(tag)}>
<Grid
key={tag.id}
item
className={classes.dropdownItem}
onClick={() => handleXCAtagChange(tag)}
>
{tag.upload_name}
</Grid>
))}
</Grid>}
</Grid>
)}
</Grid>
)}
))}
</Grid>
</Grid>
</Grid>
62 changes: 51 additions & 11 deletions js/components/preview/molecule/redux/dispatchActions.js
Original file line number Diff line number Diff line change
@@ -503,8 +503,9 @@ export const initializeMolecules = majorView => (dispatch, getState) => {
const state = getState();
const noTagsReceived = state.apiReducers.noTagsReceived;
const isSnapshot = state.apiReducers.isSnapshot;
const isDirectDisplay = Object.keys(state.apiReducers.direct_access || {})?.length > 0;

if (!isSnapshot) {
if (!isSnapshot && !isDirectDisplay) {
const firstTag = dispatch(getFirstTagAlphabetically());
let firstMolecule = null;
if (firstTag) {
@@ -884,15 +885,11 @@ export const applyDirectSelection = stage => (dispatch, getState) => {
if (!directAccessProcessed && directDisplay && directDisplay.molecules && directDisplay.molecules.length > 0) {
const allMols = state.apiReducers.all_mol_lists;
directDisplay.molecules.forEach(m => {
let directProteinNameModded = m.name.toLowerCase();
let directProteinCodeModded = `${directDisplay.target.toLowerCase()}-${directProteinNameModded}`;
for (let molIndex = 0; molIndex < allMols.length; molIndex++) {
let molList = allMols;
let mol = molList[molIndex];
let proteinCodeModded = mol.code.toLowerCase();
if (
m.exact ? proteinCodeModded === directProteinCodeModded : proteinCodeModded.includes(directProteinNameModded)
) {
// let directProteinNameModded = m.name.toLowerCase();
// let directProteinCodeModded = `${directDisplay.target.toLowerCase()}-${directProteinNameModded}`;
const foundMols = dispatch(searchForObservations(m.name, allMols, m.searchSettings, m.exact));
foundMols?.forEach(mol => {
if (mol) {
if (m.L && !fragmentDisplayList.includes(mol.id)) {
dispatch(addLigand(stage, mol, colourList[mol.id % colourList.length], true));
}
@@ -909,7 +906,7 @@ export const applyDirectSelection = stage => (dispatch, getState) => {
dispatch(addVector(stage, mol, colourList[mol.id % colourList.length]));
}
}
}
});
});
// dispatch(setDirectAccess({}));
dispatch(setDirectAccessProcessed(true));
@@ -1215,3 +1212,46 @@ export const updatePose = newPose => async (dispatch, getState) => {
export const createPose = newPose => async (dispatch, getState) => {
return createPoseApi(newPose);
};

const observationSearchFunctions = {
shortcode: (obs, searchTerm, exact = false) => {
if (exact) {
return obs?.code && obs.code.toLowerCase() === searchTerm.toLowerCase();
} else {
return obs?.code?.toLowerCase().includes(searchTerm.toLowerCase());
}
},
aliases: (obs, searchTerm, exact = false) => {
if (exact) {
return obs?.identifiers?.some(idf => idf.name.toLowerCase() === searchTerm.toLowerCase());
} else {
return obs?.identifiers?.some(idf => idf.name.toLowerCase().includes(searchTerm.toLowerCase()));
}
},
compoundId: (obs, searchTerm, exact = false) => {
if (exact) {
return obs?.compound_code && obs.compound_code.toLowerCase() === searchTerm.toLowerCase();
} else {
return obs?.compound_code?.toLowerCase().includes(searchTerm.toLowerCase());
}
}
};

export const searchForObservations = (searchTerm, observations, searchSettings, exact = false) => (
dispatch,
getState
) => {
if (!observations || observations.length === 0) return [];
if (!searchTerm) return observations;

let result = [];

const searchBy = searchSettings.searchBy;
const searchByKeys = Object.keys(searchBy).filter(key => searchBy[key]);

result = observations.filter(obs => {
return searchByKeys.reduce((acc, key) => acc || observationSearchFunctions[key](obs, searchTerm), false);
});

return result;
};
115 changes: 115 additions & 0 deletions js/components/preview/molecule/searchSettingsDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { memo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Grid, makeStyles, Checkbox, Typography, FormControlLabel } from '@material-ui/core';
import { Button, Modal } from '../../common';
import { setSearchSettings } from '../../../reducers/selection/actions';

const useStyles = makeStyles(theme => ({
body: {
width: '100%',
marginTop: theme.spacing(2),
marginBottom: theme.spacing(1)
},
margin: {
marginTop: theme.spacing(1)
},
checkbox: {
margin: theme.spacing(0)
}
}));

export const SearchSettingsDialog = memo(({ openDialog, setOpenDialog }) => {
const dispatch = useDispatch();
const classes = useStyles();

const searchSettings = useSelector(state => state.selectionReducers.searchSettings);

const [shortcode, setShortcode] = useState(searchSettings.searchBy.shortcode);
const [aliases, setAliases] = useState(searchSettings.searchBy.aliases);
const [compoundId, setCompoundId] = useState(searchSettings.searchBy.compoundId);

const handleCloseModal = () => {
setOpenDialog(false);
};

const handleSaveButton = () => {
dispatch(setSearchSettings({ searchBy: { shortcode, aliases, compoundId } }));
setOpenDialog(false);
};

return (
<Modal open={openDialog}>
<>
<Typography variant="h4">Search settings</Typography>
<Typography variant="subtitle1" gutterBottom className={classes.margin}>
Search by:
</Typography>
<Grid container direction="column" className={classes.body}>
<Grid item>
<FormControlLabel
control={
<Checkbox
checked={shortcode}
name="event"
color="primary"
onChange={() => {
setShortcode(prev => !prev);
}}
/>
}
label="Observation shortcode"
labelPlacement="end"
className={classes.checkbox}
/>
</Grid>
<Grid item>
<FormControlLabel
control={
<Checkbox
checked={aliases}
name="sigma"
color="primary"
onChange={() => {
setAliases(prev => !prev);
}}
/>
}
label="Compound aliases"
labelPlacement="end"
className={classes.checkbox}
/>
</Grid>
<Grid item>
<FormControlLabel
control={
<Checkbox
checked={compoundId}
name="diff"
color="primary"
onChange={() => {
setCompoundId(prev => !prev);
}}
/>
}
label="Compound ID"
labelPlacement="end"
className={classes.checkbox}
/>
</Grid>
</Grid>
<Grid container justifyContent="flex-end" direction="row">
<Grid item>
<Button color="secondary" onClick={handleCloseModal}>
Cancel
</Button>
</Grid>
<Grid item>
<Button color="primary" onClick={handleSaveButton}>
Save
</Button>
</Grid>
</Grid>
</>
</Modal>
);
});
2 changes: 1 addition & 1 deletion js/components/preview/redux/dispatchActions.js
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ export const shouldLoadProtein = ({
dispatch(setProteinIsLoading(true));
Promise.all(
nglViewList.map(nglView =>
dispatch(loadProtein(nglView)).finally(() => {
dispatch(loadProtein(nglView))?.finally(() => {
dispatch(setOrientation(nglView.id, nglView.stage.viewerControls.getOrientation()));
})
)
6 changes: 4 additions & 2 deletions js/components/snapshot/redux/utilitySnapshotShapes.js
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@ export const SNAPSHOT_VALUES_TO_BE_DELETED = {
rhsDataIsLoading: true,
rhsDataIsLoaded: false,
proteinIsLoading: false,
proteinIsLoaded: false
proteinIsLoaded: false,
compound_identifiers: []
},
nglReducers: {
objectsInView: {},
@@ -69,7 +70,8 @@ export const SNAPSHOT_VALUES_NOT_TO_BE_DELETED_SWITCHING_TARGETS = {
moleculeTags: [],
tagList: [],
categoryList: [],
lhs_compounds_list: []
lhs_compounds_list: [],
compound_identifiers: []
},
datasetsReducers: {
datasets: [],
18 changes: 16 additions & 2 deletions js/components/target/withLoadingTargetIdList.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { memo, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { loadLegacyTargetList, loadTargetList } from './redux/dispatchActions';
import { useDispatch, useSelector } from 'react-redux';
import { getTargetProjectCombinations, loadLegacyTargetList, loadTargetList } from './redux/dispatchActions';
import { setTargetIdList } from '../../reducers/api/actions';

export const withLoadingTargetList = WrappedComponent => {
return memo(() => {
const dispatch = useDispatch();
const targetIdList = useSelector(state => state.apiReducers.target_id_list);
const projects = useSelector(state => state.targetReducers.projects);

useEffect(() => {
let onCancel = () => {};
@@ -17,6 +20,17 @@ export const withLoadingTargetList = WrappedComponent => {
};
}, [dispatch]);

useEffect(() => {
if (targetIdList && targetIdList.length > 0 && projects && projects.length > 0) {
const firstTarget = targetIdList[0];
if (typeof firstTarget.project !== 'object') {
const combinations = getTargetProjectCombinations(targetIdList, projects);
const updatedTargets = combinations.map(c => c.updatedTarget);
dispatch(setTargetIdList(updatedTargets));
}
}
}, [dispatch, targetIdList, projects]);

return <WrappedComponent />;
});
};
86 changes: 50 additions & 36 deletions js/reducers/selection/actions.js
Original file line number Diff line number Diff line change
@@ -4,14 +4,14 @@

import { constants } from './constants';

export const setToBuyList = function (to_buy_list) {
export const setToBuyList = function(to_buy_list) {
return {
type: constants.SET_TO_BUY_LIST,
to_buy_list: to_buy_list
};
};

export const appendToBuyList = function (item, index, skipTracking = false) {
export const appendToBuyList = function(item, index, skipTracking = false) {
return {
type: constants.APPEND_TO_BUY_LIST,
item: item,
@@ -20,7 +20,7 @@ export const appendToBuyList = function (item, index, skipTracking = false) {
};
};

export const removeFromToBuyList = function (item, index, skipTracking = false) {
export const removeFromToBuyList = function(item, index, skipTracking = false) {
return {
type: constants.REMOVE_FROM_TO_BUY_LIST,
item: item,
@@ -29,21 +29,21 @@ export const removeFromToBuyList = function (item, index, skipTracking = false)
};
};

export const appendToBuyListAll = function (items) {
export const appendToBuyListAll = function(items) {
return {
type: constants.APPEND_TO_BUY_LIST_ALL,
items: items
};
};

export const removeFromToBuyListAll = function (items) {
export const removeFromToBuyListAll = function(items) {
return {
type: constants.REMOVE_FROM_BUY_LIST_ALL,
items: items
};
};

export const setVectorList = function (vectList) {
export const setVectorList = function(vectList) {
return {
type: constants.SET_VECTOR_LIST,
vector_list: vectList
@@ -57,94 +57,94 @@ export const setCurrentVector = vectorSmile => {
};
};

export const setFragmentDisplayList = function (fragmentDisplayList, skipTracking = false) {
export const setFragmentDisplayList = function(fragmentDisplayList, skipTracking = false) {
return {
type: constants.SET_FRAGMENT_DISPLAY_LIST,
fragmentDisplayList: fragmentDisplayList,
skipTracking
};
};

export const appendFragmentDisplayList = function (item, skipTracking = false) {
export const appendFragmentDisplayList = function(item, skipTracking = false) {
return {
type: constants.APPEND_FRAGMENT_DISPLAY_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromFragmentDisplayList = function (item, skipTracking = false) {
export const removeFromFragmentDisplayList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_FRAGMENT_DISPLAY_LIST,
item: item,
skipTracking: skipTracking
};
};

export const setProteinList = function (proteinList, skipTracking = false) {
export const setProteinList = function(proteinList, skipTracking = false) {
return {
type: constants.SET_PROTEIN_LIST,
proteinList: proteinList,
skipTracking
};
};

export const appendProteinList = function (item, skipTracking = false) {
export const appendProteinList = function(item, skipTracking = false) {
return {
type: constants.APPEND_PROTEIN_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromProteinList = function (item, skipTracking = false) {
export const removeFromProteinList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_PROTEIN_LIST,
item: item,
skipTracking: skipTracking
};
};
export const setComplexList = function (complexList, skipTracking = false) {
export const setComplexList = function(complexList, skipTracking = false) {
return {
type: constants.SET_COMPLEX_LIST,
complexList: complexList,
skipTracking
};
};

export const appendComplexList = function (item, skipTracking = false) {
export const appendComplexList = function(item, skipTracking = false) {
return {
type: constants.APPEND_COMPLEX_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromComplexList = function (item, skipTracking = false) {
export const removeFromComplexList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_COMPLEX_LIST,
item: item,
skipTracking: skipTracking
};
};

export const setSurfaceList = function (surfaceList, skipTracking = false) {
export const setSurfaceList = function(surfaceList, skipTracking = false) {
return {
type: constants.SET_SURFACE_LIST,
surfaceList: surfaceList,
skipTracking
};
};

export const appendSurfaceList = function (item, skipTracking = false) {
export const appendSurfaceList = function(item, skipTracking = false) {
return {
type: constants.APPEND_SURFACE_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromSurfaceList = function (item, skipTracking = false) {
export const removeFromSurfaceList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_SURFACE_LIST,
item: item,
@@ -168,107 +168,107 @@ export const removeFromDensityListType = (item, skipTracking = false) => {
};
};

export const setDensityList = function (densityList) {
export const setDensityList = function(densityList) {
return {
type: constants.SET_DENSITY_LIST,
densityList: densityList
};
};

export const appendDensityList = function (item, skipTracking = false) {
export const appendDensityList = function(item, skipTracking = false) {
return {
type: constants.APPEND_DENSITY_LIST,
item: item,
skipTracking
};
};

export const removeFromDensityList = function (item, skipTracking = false) {
export const removeFromDensityList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_DENSITY_LIST,
item: item,
skipTracking
};
};

export const setDensityListCustom = function (densityListCustom) {
export const setDensityListCustom = function(densityListCustom) {
return {
type: constants.SET_DENSITY_LIST_CUSTOM,
densityListCustom: densityListCustom
};
};

export const appendDensityListCustom = function (item, skipTracking = false) {
export const appendDensityListCustom = function(item, skipTracking = false) {
return {
type: constants.APPEND_DENSITY_LIST_CUSTOM,
item: item,
skipTracking
};
};

export const removeFromDensityListCustom = function (item, skipTracking = false) {
export const removeFromDensityListCustom = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_DENSITY_LIST_CUSTOM,
item: item,
skipTracking
};
};

export const setQualityList = function (qualityList, skipTracking = false) {
export const setQualityList = function(qualityList, skipTracking = false) {
return {
type: constants.SET_QUALITY_LIST,
qualityList: qualityList,
skipTracking
};
};

export const appendInformationList = function (item) {
export const appendInformationList = function(item) {
return {
type: constants.APPEND_INFORMATION_LIST,
item: item
};
};

export const removeFromInformationList = function (item) {
export const removeFromInformationList = function(item) {
return {
type: constants.REMOVE_FROM_INFORMATION_LIST,
item: item
};
};

export const appendQualityList = function (item, skipTracking = false) {
export const appendQualityList = function(item, skipTracking = false) {
return {
type: constants.APPEND_QUALITY_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromQualityList = function (item, skipTracking = false) {
export const removeFromQualityList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_QUALITY_LIST,
item: item,
skipTracking: skipTracking
};
};

export const setVectorOnList = function (vectorOnList, skipTracking = false) {
export const setVectorOnList = function(vectorOnList, skipTracking = false) {
return {
type: constants.SET_VECTOR_ON_LIST,
vectorOnList: vectorOnList,
skipTracking
};
};

export const appendVectorOnList = function (item, skipTracking = false) {
export const appendVectorOnList = function(item, skipTracking = false) {
return {
type: constants.APPEND_VECTOR_ON_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromVectorOnList = function (item, skipTracking = false) {
export const removeFromVectorOnList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_VECTOR_ON_LIST,
item: item,
@@ -283,7 +283,7 @@ export const reloadSelectionReducer = savedSelectionReducers => {
};
};

export const resetSelectionState = function () {
export const resetSelectionState = function() {
return {
type: constants.RESET_SELECTION_STATE
};
@@ -374,23 +374,23 @@ export const setArrowUpDown = (item, newItem, arrowType, data) => ({
}
});

export const setSelectedTagList = function (selectedTagList, skipTracking = false) {
export const setSelectedTagList = function(selectedTagList, skipTracking = false) {
return {
type: constants.SET_SELECTED_TAG_LIST,
selectedTagList: selectedTagList,
skipTracking
};
};

export const appendSelectedTagList = function (item, skipTracking = false) {
export const appendSelectedTagList = function(item, skipTracking = false) {
return {
type: constants.APPEND_SELECTED_TAG_LIST,
item: item,
skipTracking: skipTracking
};
};

export const removeFromSelectedTagList = function (item, skipTracking = false) {
export const removeFromSelectedTagList = function(item, skipTracking = false) {
return {
type: constants.REMOVE_FROM_SELECTED_TAG_LIST,
item: item,
@@ -652,3 +652,17 @@ export const updateInToBeDisplayedList = item => {
item: item
};
};

export const setSearchSettingsDialogOpen = isOpen => {
return {
type: constants.SET_SEARCH_SETTINGS_DIALOG_OPEN,
isOpen: isOpen
};
};

export const setSearchSettings = settings => {
return {
type: constants.SET_SEARCH_SETTINGS,
settings: settings
};
};
5 changes: 4 additions & 1 deletion js/reducers/selection/constants.js
Original file line number Diff line number Diff line change
@@ -110,7 +110,10 @@ export const constants = {
SET_TO_BE_DISPLAYED_LIST: prefix + 'SET_TO_BE_DISPLAYED_LIST',
APPEND_TO_BE_DISPLAYED_LIST: prefix + 'APPEND_TO_BE_DISPLAYED_LIST',
REMOVE_FROM_TO_BE_DISPLAYED_LIST: prefix + 'REMOVE_FROM_TO_BE_DISPLAYED_LIST',
UPDATE_IN_TO_BE_DISPLAYED_LIST: prefix + 'UPDATE_IN_TO_BE_DISPLAYED_LIST'
UPDATE_IN_TO_BE_DISPLAYED_LIST: prefix + 'UPDATE_IN_TO_BE_DISPLAYED_LIST',

SET_SEARCH_SETTINGS_DIALOG_OPEN: prefix + 'SET_SEARCH_SETTINGS_DIALOG_OPEN',
SET_SEARCH_SETTINGS: prefix + 'SET_SEARCH_SETTINGS'
};

export const PREDEFINED_FILTERS = {
16 changes: 15 additions & 1 deletion js/reducers/selection/selectionReducers.js
Original file line number Diff line number Diff line change
@@ -68,11 +68,25 @@ export const INITIAL_STATE = {

// display: true
// }
toBeDisplayedList: []
toBeDisplayedList: [],
searchSettingsDialogOpen: false,
searchSettings: {
searchBy: {
shortcode: true,
aliases: true,
compoundId: true
}
}
};

export function selectionReducers(state = INITIAL_STATE, action = {}) {
switch (action.type) {
case constants.SET_SEARCH_SETTINGS_DIALOG_OPEN:
return { ...state, searchSettingsDialogOpen: action.isOpen };

case constants.SET_SEARCH_SETTINGS:
return { ...state, searchSettings: JSON.parse(JSON.stringify(action.settings)) };

case constants.SET_TO_BE_DISPLAYED_LIST:
return { ...state, toBeDisplayedList: action.toBeDisplayedList };

0 comments on commit 6681d51

Please sign in to comment.