From f615596bb2d03cc2f965eb3df55ab65574e6a04c Mon Sep 17 00:00:00 2001 From: boriskovar-m2ms Date: Wed, 24 Jul 2024 12:33:52 +0200 Subject: [PATCH] Implements #1465, #1475, #1322, #1460 (#439) * #1465 changed /funders URL to point to landing page with open funders modal and also use this URL clicking on "Contributors" button in header, updated Route with 5.1 preferred style * #1475 removed "Set as group name" for observations * #1322 changed font weight for pose and observation titles, keep open observation modal on changing poses * #1322 changed "group" to "pose", leave the LPC buttons visible, but grey them out without selection * #1465 reverted "Contributors" button behavior, added copy funders button to funders modal * #1465 added text to copy url button * #1322 highlight selected pose, use singular names of tag categories, added expanded view mode, select all displayed and select all buttons, calculate height of window based on available space * fixed some missing keys and pagination options for targets and projects * #1460 allow to edit all tag categories, show colour and category in dropdown list and updated its sorting --------- Co-authored-by: matej --- js/components/funders/fundersModal.js | 24 +- js/components/header/index.js | 6 +- js/components/landing/Landing.js | 14 +- .../molecule/moleculeView/moleculeView.js | 66 ++- .../observationCmpView/observationCmpView.js | 37 +- .../preview/molecule/observationsDialog.js | 426 +++++++++++------- .../preview/tags/details/tagDetailRow.js | 11 +- .../preview/tags/details/tagDetails.js | 7 +- .../preview/tags/modal/editTagsModal.js | 78 ++-- js/components/preview/tags/utils/tagUtils.js | 14 + js/components/projects/index.js | 7 +- js/components/routes/Routes.js | 64 ++- js/components/services/ServicesStatus.js | 6 +- .../services/ServicesStatusWrapper.js | 2 +- js/components/target/targetList.js | 21 +- js/constants/constants.js | 8 + 16 files changed, 518 insertions(+), 273 deletions(-) diff --git a/js/components/funders/fundersModal.js b/js/components/funders/fundersModal.js index 1a92b4c42..3480c72f5 100644 --- a/js/components/funders/fundersModal.js +++ b/js/components/funders/fundersModal.js @@ -2,16 +2,24 @@ * This is a modal window wrapper for funders. */ -import React, { memo } from 'react'; +import React, { memo, useContext } from 'react'; import Modal from '../common/Modal'; -import { Grid, makeStyles, Typography } from '@material-ui/core'; +import { Button, Grid, IconButton, makeStyles, Typography } from '@material-ui/core'; import { CONTRIBUTORS, FUNDING, get_logo } from './constants'; import { Tooltip } from '@mui/material'; +import { URLS } from '../routes/constants'; +import { ContentCopyRounded } from '@mui/icons-material'; +import { ToastContext } from '../toast'; const COLUMNS = 5; const MAX_IMAGE_HEIGHT = 90; const useStyles = makeStyles(theme => ({ + copyButton: { + position: 'absolute', + top: 0, + right: 0 + }, imageItem: { paddingTop: '3px', paddingBottom: '3px', @@ -39,6 +47,8 @@ const useStyles = makeStyles(theme => ({ export const FundersModal = memo(({ openModal, onModalClose }) => { const classes = useStyles(); + const { toastInfo } = useContext(ToastContext); + if (openModal === undefined) { console.log('undefined openModal'); onModalClose(); @@ -49,8 +59,18 @@ export const FundersModal = memo(({ openModal, onModalClose }) => { window.open(link, 'blank'); }; + const copyFundersLink = async () => { + await navigator.clipboard.writeText(window.location.hostname + URLS.funders); + toastInfo('Link was copied to the clipboard', { autoHideDuration: 5000 }); + }; + return ( onModalClose()}> + + + Funding and support: {FUNDING.map((company, i) => diff --git a/js/components/header/index.js b/js/components/header/index.js index 0c30a8003..73d87eb5b 100644 --- a/js/components/header/index.js +++ b/js/components/header/index.js @@ -111,7 +111,7 @@ const useStyles = makeStyles(theme => ({ })); export default memo( - forwardRef(({ headerHeight = 0, setHeaderHeight }, ref) => { + forwardRef(({ headerHeight = 0, setHeaderHeight, isFundersLink = false }, ref) => { const dispatch = useDispatch(); let history = useHistory(); const classes = useStyles(); @@ -141,6 +141,10 @@ export default memo( const targetDiscourseVisible = discourseAvailable && targetName; const projectDiscourseVisible = discourseAvailable && currentProject && currentProject.title; + useEffect(() => { + setOpenFunders(isFundersLink); + }, [isFundersLink]); + useEffect(() => { getVersions() .then(response => { diff --git a/js/components/landing/Landing.js b/js/components/landing/Landing.js index 97fdfb011..f505d3407 100644 --- a/js/components/landing/Landing.js +++ b/js/components/landing/Landing.js @@ -2,7 +2,7 @@ * Created by ricgillams on 21/06/2018. */ import { Grid, Link, makeStyles } from '@material-ui/core'; -import React, { memo, useContext, useEffect, useState } from 'react'; +import React, { memo, useCallback, useContext, useEffect, useState } from 'react'; import { TargetList } from '../target/targetList'; import { connect } from 'react-redux'; import * as apiActions from '../../reducers/api/actions'; @@ -13,7 +13,6 @@ import { resetCurrentCompoundsSettings } from '../preview/compounds/redux/action import { resetProjectsReducer } from '../projects/redux/actions'; import { withLoadingProjects } from '../target/withLoadingProjects'; import { ToastContext } from '../toast'; -import { HeaderContext } from '../header/headerContext'; const useStyles = makeStyles(theme => ({ root: { @@ -33,7 +32,6 @@ const Landing = memo( const [targetListWidth, setTargetListWidth] = useState(450); const [projectListWidth, setProjectListWidth] = useState(projectWidth); - const { setSnackBarTitle } = useContext(HeaderContext); const { toast } = useContext(ToastContext); const [loginText, setLoginText] = useState( DJANGO_CONTEXT['username'] === 'NOT_LOGGED_IN' ? '' : "You're logged in as " + DJANGO_CONTEXT['username'] @@ -69,19 +67,19 @@ const Landing = memo( setIsResizing(true); }; - const handleMouseMove = e => { + const handleMouseMove = useCallback(e => { if (!isResizing) return; const targetListWidth = e.clientX; const projectListWidth = window.innerWidth - targetListWidth; setTargetListWidth(targetListWidth); setProjectListWidth(projectListWidth); - }; + }, [isResizing]); - const handleMouseUp = () => { + const handleMouseUp = useCallback(() => { setIsResizing(false); window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); - }; + }, [handleMouseMove]); useEffect(() => { if (isResizing) { @@ -91,7 +89,7 @@ const Landing = memo( window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); } - }, [isResizing]); + }, [isResizing, handleMouseMove, handleMouseUp]); return ( diff --git a/js/components/preview/molecule/moleculeView/moleculeView.js b/js/components/preview/molecule/moleculeView/moleculeView.js index c4fc6964b..cf0c74fa8 100644 --- a/js/components/preview/molecule/moleculeView/moleculeView.js +++ b/js/components/preview/molecule/moleculeView/moleculeView.js @@ -9,7 +9,7 @@ import { Panel } from '../../../common'; import { MyLocation, Warning, Assignment, AssignmentTurnedIn } from '@material-ui/icons'; import SVGInline from 'react-svg-inline'; import classNames from 'classnames'; -import { VIEWS } from '../../../../constants/constants'; +import { PLURAL_TO_SINGULAR, VIEWS } from '../../../../constants/constants'; import { NGL_PARAMS, COMMON_PARAMS } from '../../../nglView/constants'; import { NglContext } from '../../../nglView/nglProvider'; import { @@ -52,7 +52,6 @@ import { getRandomColor } from '../utils/color'; import { DEFAULT_TAG_COLOR, getAllTagsForCategories, getAllTagsForLHSCmp, getAllTagsForMol, getAllTagsForObservation, getAllTagsForObservationPopover } from '../../tags/utils/tagUtils'; import MoleculeSelectCheckbox from './moleculeSelectCheckbox'; import useClipboard from 'react-use-clipboard'; -import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import { Edit } from '@material-ui/icons'; import { DJANGO_CONTEXT } from '../../../../utils/djangoContext'; @@ -138,7 +137,8 @@ const useStyles = makeStyles(theme => ({ border: 'solid 1px', borderColor: theme.palette.background.divider, borderStyle: 'solid solid solid solid', - width: 'inherit' + minWidth: 327 + // width: 'inherit' }, image: { border: 'solid 1px', @@ -192,19 +192,16 @@ const useStyles = makeStyles(theme => ({ whiteSpace: 'nowrap', textOverflow: 'ellipsis', lineHeight: '1.45', - fontWeight: 500, - fontSize: '0.9rem', + // fontWeight: 500, + fontSize: '0.8rem', letterSpacing: '0.02em' }, + moleculeTitleLabelMain: { + fontWeight: 'bold', + fontSize: '0.9rem' + }, moleculeTitleLabelMainObs: { - paddingLeft: 2, - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - lineHeight: '1.45', - fontWeight: 500, - fontSize: '0.9rem', - letterSpacing: '0.02em', + fontWeight: 'bolder', // fontStyle: 'italic', textDecorationLine: 'underline', textDecorationStyle: 'dotted' @@ -345,6 +342,11 @@ const useStyles = makeStyles(theme => ({ }, buttonSelectedLoadingOverlay: { color: theme.palette.primary.contrastText + }, + categoryCell: { + padding: '4px 8px', + fontWeight: 'bold', + textWrap: 'nowrap' } })); @@ -379,7 +381,8 @@ const MoleculeView = memo( disableL, disableP, disableC, - hideImage + hideImage, + showExpandedView }) => { // const [countOfVectors, setCountOfVectors] = useState('-'); // const [cmpds, setCmpds] = useState('-'); @@ -1108,13 +1111,11 @@ const MoleculeView = memo( e.preventDefault(); setNameCopied(moleculeTitle); }} - className={ - data.id === pose?.main_site_observation - ? classes.moleculeTitleLabelMainObs - : classes.moleculeTitleLabel - } + className={classNames(classes.moleculeTitleLabel, { [classes.moleculeTitleLabelMainObs]: data.id === pose?.main_site_observation })} > - {moleculeTitleTruncated} + {moleculeTitleTruncated} +
+ {data?.compound_code}
{/* Molecule properties */} @@ -1356,13 +1357,14 @@ const MoleculeView = memo( justifyContent="center" alignItems="center" // wrap="nowrap" - style={{ height: "100%" }}> + style={{ height: "100%" }} + > {['CanonSites', 'ConformerSites', 'CrystalformSites', 'Crystalforms', 'Quatassemblies'].map(tagCategory => { const tagTypeObject = getTagType(tagCategory); const tagLabel = tagCategory === 'ConformerSites' ? tagTypeObject.tag_prefix.replace(getTagType('CanonSites')?.tag_prefix, '') : tagTypeObject?.tag_prefix; return {tagCategory} - {tagTypeObject.upload_name}} + title={
{PLURAL_TO_SINGULAR[tagCategory]} - {tagTypeObject.upload_name}
} > } + {showExpandedView && + {['CanonSites', 'ConformerSites', 'CrystalformSites', 'Crystalforms', 'Quatassemblies'].map((tagCategory, index) => { + const tagTypeObject = getTagType(tagCategory); + let tagLabel = ''; + if (tagTypeObject) { + if (tagCategory === 'CrystalformSites') { + // "chop" more of CrystalformSites name + tagLabel = tagTypeObject.upload_name.substring(tagTypeObject.upload_name.indexOf('-') + 1); + tagLabel = tagLabel.substring(tagLabel.indexOf('-') + 1); + } else { + tagLabel = tagTypeObject.upload_name.substring(tagTypeObject.upload_name.indexOf('-') + 1); + } + } + return + + {tagLabel} + + + })} + }
({ color: 'black', height: 54 }, + siteOpenObservations: { + // instead of coloring every specific part of border, just use inner shadow to fake it + boxShadow: 'inset 0 0 0 2px ' + theme.palette.primary.main + }, buttonsRow: { lineHeight: '1' }, @@ -215,14 +219,18 @@ const useStyles = makeStyles(theme => ({ }, moleculeTitleLabel: { paddingLeft: 2, - fontWeight: 500, + // fontWeight: 400, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', lineHeight: '1.45', - fontSize: '0.9rem', + fontSize: '0.8rem', letterSpacing: '0.02em' }, + moleculeTitleLabelMain: { + fontWeight: 'bold', + fontSize: '0.9rem' + }, checkbox: { padding: 0 }, @@ -1256,7 +1264,7 @@ const ObservationCmpView = memo( container justifyContent="space-between" direction="row" - className={classes.container} + className={classNames(classes.container, { [classes.siteOpenObservations]: poseIdForObservationsDialog === data.id && isObservationDialogOpen })} wrap="nowrap" ref={ref} > @@ -1300,10 +1308,8 @@ const ObservationCmpView = memo( }} className={classes.moleculeTitleLabel} > - - {data?.code.replaceAll(`${target_on_name}-`, '')} -
-
+ {getMainObservation()?.code.replaceAll(`${target_on_name}-`, '')} +
{data?.main_site_observation_cmpd_code} @@ -1575,7 +1581,7 @@ const ObservationCmpView = memo( wrap="nowrap"> CanonSites - {getCanonSitesTag().upload_name}} + title={
CanonSite - {getCanonSitesTag().upload_name}
} > ConformerSites - {conformerSite.upload_name}} + title={
ConformerSite - {conformerSite.upload_name}
} > = 3 @@ -1644,10 +1650,15 @@ const ObservationCmpView = memo( onClick={() => { // setLoadingInspiration(true); - if (!isObservationDialogOpen) { + // do not close modal on pose change + if (!isObservationDialogOpen || poseIdForObservationsDialog !== data.id) { dispatch(setObservationsForLHSCmp(observations)); } - dispatch(setOpenObservationsDialog(!isObservationDialogOpen)); + if (poseIdForObservationsDialog !== data.id || poseIdForObservationsDialog === 0 || (poseIdForObservationsDialog === data.id && !isObservationDialogOpen)) { + dispatch(setOpenObservationsDialog(true)); + } else { + dispatch(setOpenObservationsDialog(false)); + } dispatch(setPoseIdForObservationsDialog(data.id)); if (setRef) { diff --git a/js/components/preview/molecule/observationsDialog.js b/js/components/preview/molecule/observationsDialog.js index a2c7c01f1..ac9057ef6 100644 --- a/js/components/preview/molecule/observationsDialog.js +++ b/js/components/preview/molecule/observationsDialog.js @@ -1,11 +1,11 @@ -import React, { forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import React, { forwardRef, memo, useCallback, useContext, useMemo, useRef, useState } from 'react'; import { CircularProgress, Grid, Popper, IconButton, Typography, Tooltip } from '@material-ui/core'; -import { Close, KeyboardArrowDown } from '@material-ui/icons'; +import { ArrowLeft, ArrowRight, Close, KeyboardArrowDown } from '@material-ui/icons'; import { makeStyles } from '@material-ui/styles'; import { useDispatch, useSelector } from 'react-redux'; import classNames from 'classnames'; import { NglContext } from '../../nglView/nglProvider'; -import { VIEWS } from '../../../constants/constants'; +import { PLURAL_TO_SINGULAR, VIEWS } from '../../../constants/constants'; import { changeButtonClassname } from '../../datasets/helpers'; import { addComplex, @@ -16,7 +16,6 @@ import { copyPoseToPoseDTO, createNewPose, getAllCompatiblePoses, - prepareEmptyPoseDTO, removeComplex, removeHitProtein, removeLigand, @@ -29,6 +28,8 @@ import { } from './redux/dispatchActions'; import { colourList } from './utils/color'; import { + appendToMolListToEdit, + removeFromMolListToEdit, setDeselectedAllByType, setOpenObservationsDialog, setSelectedAllByType, @@ -45,15 +46,18 @@ import { DJANGO_CONTEXT } from '../../../utils/djangoContext'; import { updateLHSCompound } from '../../../reducers/api/actions'; import { createPoseErrorMessage } from './api/poseApi'; +const MIN_PANEL_HEIGHT = 250; + const useStyles = makeStyles(theme => ({ paper: { - width: 358, - // minHeight: 294, - overflowY: 'hidden' + minWidth: 372, + minHeight: MIN_PANEL_HEIGHT, + overflowY: 'hidden', + resize: 'vertical' }, molHeader: { - marginLeft: 19, - width: 'calc(100% - 19px)' + marginLeft: 19 + // width: 'calc(100% - 19px)' }, rightBorder: { borderRight: '1px solid', @@ -74,9 +78,21 @@ const useStyles = makeStyles(theme => ({ headerButton: { paddingTop: 10 }, + container: { + // minHeight: '100px', + height: '100%' + // width: 'inherit', + // color: theme.palette.black + }, content: { overflowY: 'auto', - height: 214 + width: '100%', + // height: 214 + // minHeight: '77%', + // height: '77%' + // minHeight: 200, + // marginBottom: -43 + height: `calc(99% - ${theme.spacing(6)}px - ${theme.spacing(2)}px)` }, search: { width: 140 @@ -84,11 +100,6 @@ const useStyles = makeStyles(theme => ({ notFound: { paddingTop: theme.spacing(2) }, - contButtonsMargin: { - marginTop: theme.spacing(1) / 2, - marginBottom: theme.spacing(1) / 2, - marginLeft: theme.spacing(2) - }, contColButton: { minWidth: 'fit-content', paddingLeft: theme.spacing(1) / 4, @@ -106,7 +117,8 @@ const useStyles = makeStyles(theme => ({ }, '&:disabled': { borderRadius: 0, - borderColor: 'white' + borderColor: 'white', + backgroundColor: '#c5cae9' } }, contColButtonSelected: { @@ -128,8 +140,14 @@ const useStyles = makeStyles(theme => ({ contColButtonBottomRow: { border: '1px solid' }, + topRow: { + marginBottom: theme.spacing(1) / 4 + }, bottomRow: { - marginTop: theme.spacing(1) / 4 + marginTop: theme.spacing(1) / 4, + '& > div': { + marginLeft: 12 + } }, dropdownContent: { display: 'none', @@ -157,6 +175,21 @@ const useStyles = makeStyles(theme => ({ '&:hover $dropdownContent': { display: 'flex' } + }, + popoutIcon: { + position: 'absolute', + top: 30, + left: 334, + backgroundColor: theme.palette.white, + borderRadius: 5, + '& > *': { + paddingRight: 0 + } + }, + headerCell: { + padding: '0px 12px', + borderColor: 'black', + fontSize: 12 } })); @@ -211,6 +244,7 @@ export const ObservationsDialog = memo( const tagEditorRef = useRef(); const [tagEditorAnchorEl, setTagEditorAnchorEl] = useState(null); + const [expandView, setExpandView] = useState(null); const moleculeList = useMemo(() => { if (searchString !== null) { @@ -226,16 +260,20 @@ export const ObservationsDialog = memo( [moleculesToEditIds, observationsDataList] ); + const isLigandOn = ligandList.some(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID)); + const isProteinOn = proteinList.some(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID)); + const isComplexOn = complexList.some(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID)); + // TODO: refactor from this line (duplicity in datasetMoleculeList.js) - const isLigandOn = changeButtonClassname( + const isLigandOnForClassname = changeButtonClassname( ligandList.filter(moleculeID => allSelectedMolecules.find(molecule => molecule.id === moleculeID) !== undefined), allSelectedMolecules ); - const isProteinOn = changeButtonClassname( + const isProteinOnForClassname = changeButtonClassname( proteinList.filter(moleculeID => allSelectedMolecules.find(molecule => molecule.id === moleculeID) !== undefined), allSelectedMolecules ); - const isComplexOn = changeButtonClassname( + const isComplexOnForClassname = changeButtonClassname( complexList.filter(moleculeID => allSelectedMolecules.find(molecule => molecule.id === moleculeID) !== undefined), allSelectedMolecules ); @@ -338,6 +376,33 @@ export const ObservationsDialog = memo( } }; + const areAllMoleculesSelected = allSelectedMolecules.length === moleculeList.length; + + const handleSelectAllObservations = () => { + if (areAllMoleculesSelected) { + allSelectedMolecules.forEach(molecule => dispatch(removeFromMolListToEdit(molecule.id))); + } else { + moleculeList.filter(molecule => !moleculesToEditIds.includes(molecule.id)).forEach(molecule => dispatch(appendToMolListToEdit(molecule.id))); + } + } + + /** + * Select all displayed observations, i.e. have displayed some of their L, P, C + */ + const handleSelectAllDisplayedObservations = () => { + // get all displayed but filter out already selected and duplicates + // lists of IDs + let displayedObservations = ligandList.filter(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID) && !allSelectedMolecules.some(molecule => molecule.id === moleculeID)); + + const displayedProteins = proteinList.filter(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID) && !allSelectedMolecules.some(molecule => molecule.id === moleculeID) && !displayedObservations.some(molecule => molecule === moleculeID)); + displayedObservations = displayedObservations.concat(displayedProteins); + + const displayedComplexes = complexList.filter(moleculeID => moleculeList.some(molecule => molecule.id === moleculeID) && !allSelectedMolecules.some(molecule => molecule.id === moleculeID) && !displayedObservations.some(molecule => molecule === moleculeID)); + displayedObservations = displayedObservations.concat(displayedComplexes); + + displayedObservations.forEach(moleculeID => dispatch(appendToMolListToEdit(moleculeID))); + } + const getSelectedMoleculesByType = (type, isAdd) => { switch (type) { case 'ligand': @@ -367,30 +432,6 @@ export const ObservationsDialog = memo( buttonState => buttonState ); - const handleSetAsGroupName = () => { - const firstSelectedObs = observationsDataList.find(molecule => moleculesToEditIds.includes(molecule.id)); - if (firstSelectedObs) { - const pose = poses.find(pose => pose.id === firstSelectedObs.pose); - pose.display_name = firstSelectedObs.code; - pose.code = pose.display_name; - const poseDTO = copyPoseToPoseDTO(pose); - dispatch(updatePose(poseDTO)) - .then(resp => { - dispatch(updateLHSCompound(pose)); - toastInfo(`Group name was changed to ${pose.display_name}`, { autoHideDuration: 5000 }); - }) - .catch(err => { - console.log(err); - const errorMessage = createPoseErrorMessage(err); - if (errorMessage) { - toastError(errorMessage, { autoHideDuration: 600000 }); - } else { - toastError(err, { autoHideDuration: 600000 }); - } - }); - } - }; - const handleSetMainObservation = () => { const firstSelectedObs = observationsDataList.find(molecule => moleculesToEditIds.includes(molecule.id)); if (firstSelectedObs) { @@ -493,7 +534,29 @@ export const ObservationsDialog = memo( } }; - // TODO refactor to this line + /** + * Calculate available height for dialog window + * + * @returns {Number} + */ + const getPanelHeight = () => { + let height = 0; + // available height of the window - top position of the anchor element, ie pose from hit navigator - "bottom margin" + const maxHeight = window.innerHeight - anchorEl.getBoundingClientRect().top - 13; + const observationsApproximateHeight = moleculeList.length * 47; + const headerFooterApproximateHeight = 87; + const totalApproximateHeight = observationsApproximateHeight + headerFooterApproximateHeight; + + if (totalApproximateHeight > maxHeight) { + height = maxHeight; + } else if (totalApproximateHeight < MIN_PANEL_HEIGHT) { + height = MIN_PANEL_HEIGHT; + } else { + height = totalApproximateHeight; + } + + return height; + } return ( @@ -502,6 +565,7 @@ export const ObservationsDialog = memo( secondaryBackground title="Observations" className={classes.paper} + style={{ height: getPanelHeight() }} headerActions={[ )} {isLoadingInspirationListOfMolecules === false && moleculeList && ( - <> - - - {/* {Object.keys(moleculeProperty).map(key => ( - - {moleculeProperty[key]} - - ))} */} - {allSelectedMolecules.length > 0 && ( - + + + + + {/* {Object.keys(moleculeProperty).map(key => ( + + {moleculeProperty[key]} + + ))} */} + - - - - - - - - - - - - - {/* C stands for contacts now */} - + + + + + + + + + + + + + + + {/* C stands for contacts now */} + + + - + + + + + + + + {expandView && + {['CanonSites', 'ConformerSites', 'CrystalformSites', 'Crystalforms', 'Quatassemblies'].map((tagCategory, index) => ( + + {PLURAL_TO_SINGULAR[tagCategory]} + + ))} + } + + + setExpandView(!expandView)} + > + {expandView ? : } + + + - )} + -
+ {moleculeList.length > 0 && moleculeList.map((molecule, index, array) => { let data = molecule; @@ -637,6 +753,7 @@ export const ObservationsDialog = memo( disableC={selected && groupNglControlButtonsDisabledState.complex} setRef={setTagEditorAnchorEl} hideImage={true} + showExpandedView={expandView} /> ); @@ -654,68 +771,57 @@ export const ObservationsDialog = memo( )} -
+
{DJANGO_CONTEXT.pk && ( - - - - - - - - 0 })}> - - - handleManageGrouping(0)}> - new group from selection - - {/* TODO just a placeholder for poses here */} - {compatiblePoses?.map(pose => ( - handleManageGrouping(pose.id)}> - move selection to {pose.display_name} + + + + + + 0 })}> + + + handleManageGrouping(0)}> + new pose from selection - ))} + {/* TODO just a placeholder for poses here */} + {compatiblePoses?.map(pose => ( + handleManageGrouping(pose.id)}> + move selection to {pose.display_name} + + ))} + )} - + )} {isLoadingInspirationListOfMolecules === true && ( diff --git a/js/components/preview/tags/details/tagDetailRow.js b/js/components/preview/tags/details/tagDetailRow.js index fd84c6b64..912940829 100644 --- a/js/components/preview/tags/details/tagDetailRow.js +++ b/js/components/preview/tags/details/tagDetailRow.js @@ -1,6 +1,6 @@ import React, { memo, useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { CATEGORY_TYPE_BY_ID } from '../../../../constants/constants'; +import { CATEGORY_TYPE_BY_ID, PLURAL_TO_SINGULAR } from '../../../../constants/constants'; import TagView from '../tagView'; import { getDefaultTagDiscoursePostText } from '../utils/tagUtils'; import { DJANGO_CONTEXT } from '../../../../utils/djangoContext'; @@ -148,6 +148,11 @@ const TagDetailRow = memo(({ tag, moleculesToEditIds, moleculesToEdit }) => { dispatch(setTagToEdit(tag)); }; + const getTagCategoryName = () => { + const category = dispatch(getCategoryById(tag.category))?.category; + return PLURAL_TO_SINGULAR.hasOwnProperty(category) ? PLURAL_TO_SINGULAR[category] : category; + }; + return ( <> {/* TagView Chip */} @@ -164,9 +169,9 @@ const TagDetailRow = memo(({ tag, moleculesToEditIds, moleculesToEdit }) => { > {/* category */} - + - {dispatch(getCategoryById(tag.category))?.category} + {getTagCategoryName()} diff --git a/js/components/preview/tags/details/tagDetails.js b/js/components/preview/tags/details/tagDetails.js index a68e59f5a..b15ff41ce 100644 --- a/js/components/preview/tags/details/tagDetails.js +++ b/js/components/preview/tags/details/tagDetails.js @@ -46,6 +46,7 @@ import { Button } from '../../../common/Inputs/Button'; import { LoadingContext } from '../../../loading'; import { EditTagsModal } from '../modal/editTagsModal'; import { DJANGO_CONTEXT } from '../../../../utils/djangoContext'; +import v4 from 'uuid/v4'; export const heightOfBody = '172px'; export const defaultHeaderPadding = 15; @@ -448,7 +449,7 @@ const TagDetails = memo(() => { {DJANGO_CONTEXT.pk && ([ - + , - + ])} @@ -631,7 +632,7 @@ const TagDetails = memo(() => { {/*
*/} - + ); }); diff --git a/js/components/preview/tags/modal/editTagsModal.js b/js/components/preview/tags/modal/editTagsModal.js index 073aae8dc..77a5d2b36 100644 --- a/js/components/preview/tags/modal/editTagsModal.js +++ b/js/components/preview/tags/modal/editTagsModal.js @@ -1,9 +1,9 @@ -import React, { useContext, useEffect, useMemo, useState } from "react"; -import { Button, Checkbox, Grid, IconButton, InputLabel, MenuItem, Popper, Select, TextField, Tooltip, makeStyles, withStyles } from "@material-ui/core" +import React, { memo, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Button, Checkbox, Grid, IconButton, InputLabel, MenuItem, Popper, Select, TextField, Toolbar, Tooltip, makeStyles, withStyles } from "@material-ui/core" import { Panel } from "../../../common"; import { Close } from "@material-ui/icons"; import { useDispatch, useSelector } from "react-redux"; -import { DEFAULT_CATEGORY, DEFAULT_TAG_COLOR, augumentTagObjectWithId, compareTagsAsc, createMoleculeTagObject, getCategoriesToBeRemovedFromTagDetails, getEditNewTagCategories } from "../utils/tagUtils"; +import { DEFAULT_CATEGORY, DEFAULT_TAG_COLOR, augumentTagObjectWithId, compareTagsByCategoryAndNameAsc, createMoleculeTagObject, getCategoriesToBeRemovedFromTagDetails, getEditNewTagCategories } from "../utils/tagUtils"; import { ColorPicker } from "../../../common/Components/ColorPicker"; import { DJANGO_CONTEXT } from "../../../../utils/djangoContext"; import { createNewTag, deleteExistingTag } from "../api/tagsApi"; @@ -11,6 +11,7 @@ import { appendMoleculeTag, appendTagList, removeFromTagList, setNoTagsReceived, import { removeSelectedTag, updateTagProp } from "../redux/dispatchActions"; import { getCategoryById } from "../../molecule/redux/dispatchActions"; import { ToastContext } from "../../../toast"; +import { TAG_DETAILS_REMOVED_CATEGORIES } from "../../../../constants/constants"; const useStyles = makeStyles(theme => ({ leftSide: { @@ -30,7 +31,7 @@ const useStyles = makeStyles(theme => ({ const NEW_TAG = { id: -1, tag: '-- new tag --', category: DEFAULT_CATEGORY, colour: DEFAULT_TAG_COLOR }; -export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { +export const EditTagsModal = memo(({ open, anchorEl, setOpenDialog }) => { const classes = useStyles(); const dispatch = useDispatch(); @@ -62,16 +63,32 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { return tagCategory?.category || ''; } + const isRestricted = tag => { + return tagCategories.some(category => TAG_DETAILS_REMOVED_CATEGORIES.includes(category.category) && category.id === tag?.category); + } + + const getColourForTag = useCallback(tag => { + if (tag.colour) { + return tag.colour; + } else { + const category = dispatch(getCategoryById(tag.category)); + if (category) { + return category.colour ? `#${category.colour}` : DEFAULT_TAG_COLOR; + } else { + return DEFAULT_TAG_COLOR; + } + } + }, [dispatch]); + useEffect(() => { - const categoriesToRemove = getCategoriesToBeRemovedFromTagDetails(tagCategories); const newTagList = preTagList.filter(t => { - if (t.additional_info?.downloadName || categoriesToRemove.some(c => c.id === t.category)) { + if (t.additional_info?.downloadName) { return false; } else { return true; } }); - setTags([NEW_TAG, ...newTagList].sort(compareTagsAsc)); + setTags([NEW_TAG, ...newTagList.sort(compareTagsByCategoryAndNameAsc)]); return () => { setTag(null); setTags([NEW_TAG]); @@ -83,21 +100,12 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { if (tag.category) { setNewTagCategory(tag.category); } - if (tag.colour) { - setNewTagColor(tag.colour); - } else { - const category = dispatch(getCategoryById(tag.category)); - if (category) { - setNewTagColor(category.colour ? `#${category.colour}` : DEFAULT_TAG_COLOR); - } else { - setNewTagColor(DEFAULT_TAG_COLOR); - } - } + setNewTagColor(getColourForTag(tag)); setNewTagName(tag.tag); setNewTagLink(tag.discourse_url); setNewHidden(tag.hidden || false); } - }, [dispatch, tag]); + }, [dispatch, getColourForTag, tag]); const comboCategories = useMemo(() => { return getEditNewTagCategories(tagCategories); @@ -148,9 +156,9 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { setNewTagName(event.target.value); }; - const onHiddenForNewTagChange = event => { + const onHiddenForNewTagChange = useCallback(event => { setNewHidden(event.target.checked); - }; + }, []); const validateTag = () => { let valid = true; @@ -273,17 +281,17 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { } }; - const leftSide = text => { + const leftSide = useCallback(text => { return {text} ; - }; + }, [classes.leftSide]); - const rightSide = child => { + const rightSide = useCallback(child => { return {child} ; - }; + }, []); const CreateButton = withStyles(theme => ({ root: { @@ -323,7 +331,14 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { > {tags?.map(tag => ( - {getTagLabel(tag)} + + + {getTagCategory(tag)} + {getTagLabel(tag)} + ))} @@ -370,10 +385,12 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { Upload name
- + + +
)} @@ -409,6 +426,7 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => { // className = { classes.checkboxHeader } checked={newHidden} onChange={onHiddenForNewTagChange} + disabled={isRestricted(tag)} /> )}
@@ -466,4 +484,4 @@ export const EditTagsModal = ({ open, anchorEl, setOpenDialog }) => {
; -} \ No newline at end of file +}); diff --git a/js/components/preview/tags/utils/tagUtils.js b/js/components/preview/tags/utils/tagUtils.js index b91488cd0..95912866a 100644 --- a/js/components/preview/tags/utils/tagUtils.js +++ b/js/components/preview/tags/utils/tagUtils.js @@ -40,6 +40,20 @@ export const createMoleculeTagObject = ( }; }; +export const compareTagsByCategoryAndNameAsc = (a, b) => { + // by category first + if (a.category < b.category) { + return -1; + } + if (a.category > b.category) { + return 1; + } + // then by name + const aName = a.tag_prefix ? `${a.tag_prefix} - ${a.tag}` : a.tag; + const bName = b.tag_prefix ? `${b.tag_prefix} - ${b.tag}` : b.tag; + return aName.localeCompare(bName, undefined, { numeric: true, sensitivity: 'base' }); +}; + export const compareTagsAsc = (a, b) => { const aName = a.tag_prefix ? `${a.tag_prefix} - ${a.tag}` : a.tag; const bName = b.tag_prefix ? `${b.tag_prefix} - ${b.tag}` : b.tag; diff --git a/js/components/projects/index.js b/js/components/projects/index.js index 19a211111..35f566a02 100644 --- a/js/components/projects/index.js +++ b/js/components/projects/index.js @@ -116,10 +116,13 @@ export const Projects = memo(({ }) => { // window height for showing rows per page const [windowHeight, setWindowHeight] = useState(window.innerHeight); + const defaultRowsPerPageOptions = [20, 30, 40, 50, 100]; let projectListWindowHeight = windowHeight / 26 - 6; let projectListWindowHeightFinal = parseInt(projectListWindowHeight.toFixed(0), 10); + if (defaultRowsPerPageOptions.indexOf(projectListWindowHeightFinal) === -1) { + defaultRowsPerPageOptions.unshift(projectListWindowHeightFinal); + } const [rowsPerPage, setRowsPerPage] = useState(projectListWindowHeightFinal); - const [rowsPerPagePerPageSize, setRowsPerPagePerPageSize] = useState(projectListWindowHeightFinal); const projectItems = filteredListOfProjects ?? listOfAllProjects; @@ -840,7 +843,7 @@ export const Projects = memo(({ }) => { ({ backgroundColor: theme.palette.background.default, flex: 1 // padding: theme.spacing(1) + // style scrollbars + // '& div': { + // scrollbarWidth: 'thin', + // scrollbarColor: theme.palette.primary.main + ' ' + theme.palette.primary.light + // } } })); @@ -35,6 +40,7 @@ const Routes = memo(() => { const { headerHeight, setHeaderHeight } = useContext(HeaderContext); const contentHeight = `calc(100vh - ${headerHeight}px - ${2 * theme.spacing(1)}px)`; const contentWidth = `100%`; + const isFunders = useRouteMatch(URLS.funders) const dispatch = useDispatch(); const location = useLocation(); @@ -47,28 +53,54 @@ const Routes = memo(() => { return ( -
+
- - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + } /> + {/* } - /> - - - + /> */} + + + + {/* + + */} + + + + + + - + ); }); diff --git a/js/components/services/ServicesStatus.js b/js/components/services/ServicesStatus.js index 197ef3774..6114e3049 100644 --- a/js/components/services/ServicesStatus.js +++ b/js/components/services/ServicesStatus.js @@ -15,11 +15,11 @@ export const ServicesStatus = memo(({ services }) => { }); return - {services.map((service) => )} + {services.map((service, i) => )} }> - {services.map((service) => - + {services.map((service, i) => + )} diff --git a/js/components/services/ServicesStatusWrapper.js b/js/components/services/ServicesStatusWrapper.js index f84b7e599..36b2359da 100644 --- a/js/components/services/ServicesStatusWrapper.js +++ b/js/components/services/ServicesStatusWrapper.js @@ -15,7 +15,7 @@ export const ServicesStatusWrapper = memo(() => { current.forEach(newService => { const currentService = previous.find(previousService => previousService.id === newService.id); // remember previous value - newService.timestamp = currentService.timestamp; + newService.timestamp = currentService?.timestamp; if (currentService && currentService.state !== newService.state) { if (![SERVICE_STATUSES.OK, SERVICE_STATUSES.DEGRADED].includes(newService.state)) { newService.timestamp = Date.now(); diff --git a/js/components/target/targetList.js b/js/components/target/targetList.js index 1df9a852a..b7f592f79 100644 --- a/js/components/target/targetList.js +++ b/js/components/target/targetList.js @@ -321,12 +321,12 @@ export const TargetList = memo(() => {
{target.id}
*/} - + {target.isLegacy ? ( {target.title} @@ -338,15 +338,15 @@ export const TargetList = memo(() => { )} - +
{target.project.target_access_string}
- +
{moment(target.project.init_date).format('YYYY-MM-DD')}
- + {sgcUploaded.includes(target.title) && (
SGC summary @@ -509,10 +509,13 @@ export const TargetList = memo(() => { // window height for showing rows per page const [windowHeight, setWindowHeight] = useState(window.innerHeight); + const defaultRowsPerPageOptions = [20, 30, 40, 50, 100]; let targetListWindowHeight = windowHeight / 22.5; let targetListWindowHeightFinal = parseInt(targetListWindowHeight.toFixed(0), 10); + if (defaultRowsPerPageOptions.indexOf(targetListWindowHeightFinal) === -1) { + defaultRowsPerPageOptions.unshift(targetListWindowHeightFinal); + } const [rowsPerPage, setRowsPerPage] = useState(targetListWindowHeightFinal); - const [rowsPerPagePerPageSize, setRowsPerPagePerPageSize] = useState(targetListWindowHeightFinal); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -1113,8 +1116,8 @@ export const TargetList = memo(() => { filteredListOfTargets !== undefined ? filteredListOfTargets : listOfTargets !== undefined - ? listOfTargets - : target_id_list, + ? listOfTargets + : target_id_list, projectsList ); const slice = combinations.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); @@ -1633,7 +1636,7 @@ export const TargetList = memo(() => {