// SPDX-License-Identifier: AGPL-3.0
import React from 'react';
-import { Card, CardHeader, WithStyles, withStyles, Typography, CardContent, Tooltip } from '@material-ui/core';
-import { StyleRulesCallback } from '@material-ui/core';
+import { StyleRulesCallback, Card, CardHeader, WithStyles, withStyles, Typography, CardContent, Tooltip, Collapse } from '@material-ui/core';
import { ArvadosTheme } from 'common/custom-theme';
import { RootState } from 'store/store';
import { connect } from 'react-redux';
import { UserResource } from 'models/user';
import { UserResourceAccountStatus } from 'views-components/data-explorer/renderers';
import { FavoriteStar, PublicFavoriteStar } from 'views-components/favorite-star/favorite-star';
-import { FreezeIcon } from 'components/icon/icon';
+import { MoreVerticalIcon, FreezeIcon } from 'components/icon/icon';
import { Resource } from 'models/resource';
-import { MoreVerticalIcon } from 'components/icon/icon';
import { IconButton } from '@material-ui/core';
import { ContextMenuResource, openUserContextMenu } from 'store/context-menu/context-menu-actions';
-import { resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
-import { openContextMenu } from 'store/context-menu/context-menu-actions';
+import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
import { CollectionResource } from 'models/collection';
import { ContextMenuKind } from 'views-components/context-menu/context-menu';
import { Dispatch } from 'redux';
+import classNames from 'classnames';
+import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
type CssRules =
| 'root'
+ | 'selected'
| 'cardHeader'
| 'descriptionLabel'
| 'showMore'
| 'noDescription'
- | 'nameContainer'
+ | 'userNameContainer'
| 'cardContent'
- | 'subHeader'
| 'namePlate'
| 'faveIcon'
| 'frozenIcon'
| 'contextMenuSection'
- | 'chipSection'
| 'tag'
| 'description';
width: '100%',
marginBottom: '1rem',
flex: '0 0 auto',
- paddingTop: '0.2rem',
+ padding: 0,
+ border: '2px solid transparent',
+ },
+ selected: {
+ border: '2px solid #ccc',
},
showMore: {
color: theme.palette.primary.main,
color: theme.palette.grey['600'],
fontStyle: 'italic',
},
- nameContainer: {
+ userNameContainer: {
display: 'flex',
},
cardHeader: {
- paddingTop: '0.4rem',
+ padding: '0.2rem 0.4rem 0.1rem 1rem',
},
descriptionLabel: {
- paddingTop: '1rem',
- marginBottom: 0,
- minHeight: '2.5rem',
- marginRight: '0.8rem',
},
cardContent: {
display: 'flex',
flexDirection: 'column',
- },
- subHeader: {
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'space-between',
- marginTop: '-2rem',
+ marginTop: '-1.75rem',
},
namePlate: {
display: 'flex',
flexDirection: 'row',
+ alignItems: 'center',
+ margin: 0,
+ paddingBottom: '0.5rem',
},
faveIcon: {
fontSize: '0.8rem',
frozenIcon: {
fontSize: '0.5rem',
marginLeft: '0.3rem',
- marginTop: '0.57rem',
+ marginTop: '0.1rem',
height: '1rem',
color: theme.palette.text.primary,
},
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
- marginTop: '0.6rem',
- },
- chipSection: {
- display: 'flex',
- flexWrap: 'wrap',
+ paddingTop: '0.25rem',
},
tag: {
marginRight: '1rem',
const currentResource = getResource(currentItemUuid)(state.resources);
const frozenByUser = currentResource && getResource((currentResource as ProjectResource).frozenByUuid as string)(state.resources);
const frozenByFullName = frozenByUser && (frozenByUser as Resource & { fullName: string }).fullName;
+ const isSelected = currentItemUuid === state.detailsPanel.resourceUuid && state.detailsPanel.isOpened === true;
return {
isAdmin: state.auth.user?.isAdmin,
currentResource,
frozenByFullName,
+ isSelected,
};
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
+ handleCardClick: (uuid: string) => {
+ dispatch<any>(loadDetailsPanel(uuid));
+ },
handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: any, isAdmin: boolean) => {
event.stopPropagation();
// When viewing the contents of a filter group, all contents should be treated as read only.
if (resource.groupClass === 'filter') {
readOnly = true;
}
- const menuKind = dispatch<any>(resourceUuidToContextMenuKind(resource.uuid, readOnly));
+ let menuKind = dispatch<any>(resourceUuidToContextMenuKind(resource.uuid, readOnly));
if (menuKind === ContextMenuKind.ROOT_PROJECT) {
- dispatch<any>(openUserContextMenu(event, resource as UserResource));
- } else if (menuKind && resource) {
+ menuKind = ContextMenuKind.USER_DETAILS;
+ }
+ if (menuKind && resource) {
dispatch<any>(
openContextMenu(event, {
name: resource.name,
currentResource: ProjectResource | UserResource;
frozenByFullName?: string;
isAdmin: boolean;
+ isSelected: boolean;
handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource, isAdmin: boolean) => void;
+ handleCardClick: (resource: any) => void;
};
type UserCardProps = WithStyles<CssRules> & {
currentResource: UserResource;
isAdmin: boolean;
+ isSelected: boolean;
handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource, isAdmin: boolean) => void;
+ handleCardClick: (resource: any) => void;
};
type ProjectCardProps = WithStyles<CssRules> & {
currentResource: ProjectResource;
frozenByFullName: string | undefined;
isAdmin: boolean;
+ isSelected: boolean;
handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource, isAdmin: boolean) => void;
+ handleCardClick: (resource: any) => void;
};
export const ProjectDetailsCard = connect(
mapDispatchToProps
)(
withStyles(styles)((props: DetailsCardProps) => {
- const { classes, currentResource, frozenByFullName, handleContextMenu, isAdmin } = props;
+ const { classes, currentResource, frozenByFullName, handleContextMenu, handleCardClick, isAdmin, isSelected } = props;
+ if (!currentResource) {
+ return null;
+ }
switch (currentResource.kind as string) {
case ResourceKind.USER:
return (
classes={classes}
currentResource={currentResource as UserResource}
isAdmin={isAdmin}
+ isSelected={isSelected}
handleContextMenu={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+ handleCardClick={handleCardClick}
/>
);
case ResourceKind.PROJECT:
currentResource={currentResource as ProjectResource}
frozenByFullName={frozenByFullName}
isAdmin={isAdmin}
+ isSelected={isSelected}
handleContextMenu={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+ handleCardClick={handleCardClick}
/>
);
default:
})
);
-const UserCard: React.FC<UserCardProps> = ({ classes, currentResource, handleContextMenu, isAdmin }) => {
+const UserCard: React.FC<UserCardProps> = ({ classes, currentResource, handleContextMenu, handleCardClick, isAdmin, isSelected }) => {
const { fullName, uuid } = currentResource as UserResource & { fullName: string };
return (
- <Card className={classes.root}>
+ <Card
+ className={classNames(classes.root, isSelected ? classes.selected : '')}
+ onClick={() => handleCardClick(uuid)}
+ data-cy='user-details-card'
+ >
<CardHeader
className={classes.cardHeader}
title={
- <section className={classes.nameContainer}>
+ <section className={classes.userNameContainer}>
<Typography
noWrap
variant='h6'
>
<IconButton
aria-label='More options'
+ data-cy='kebab-icon'
onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
>
<MoreVerticalIcon />
);
};
-const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, frozenByFullName, handleContextMenu, isAdmin }) => {
- const { name, description } = currentResource as ProjectResource;
+const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, frozenByFullName, handleContextMenu, handleCardClick, isAdmin, isSelected }) => {
+ const { name, description, uuid } = currentResource as ProjectResource;
const [showDescription, setShowDescription] = React.useState(false);
- const toggleDescription = (resource: ProjectResource) => {
- console.log('toggleDescription', resource);
+ const toggleDescription = () => {
setShowDescription(!showDescription);
};
return (
- <Card className={classes.root}>
+ <Card
+ className={classNames(classes.root, isSelected ? classes.selected : '')}
+ onClick={() => handleCardClick(uuid)}
+ data-cy='project-details-card'
+ >
<CardHeader
className={classes.cardHeader}
title={
{!!frozenByFullName && (
<Tooltip
className={classes.frozenIcon}
+ disableFocusListener
title={<span>Project was frozen by {frozenByFullName}</span>}
>
<FreezeIcon style={{ fontSize: 'inherit' }} />
</section>
}
action={
- <Tooltip
- title='More options'
- disableFocusListener
- >
- <IconButton
- aria-label='More options'
- onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+ <section className={classes.contextMenuSection}>
+ <section
+ className={classes.descriptionLabel}
+ onClick={(ev) => ev.stopPropagation()}
+ >
+ {description ? (
+ <Typography
+ className={classes.showMore}
+ onClick={toggleDescription}
+ data-cy='toggle-description'
+ >
+ {!showDescription ? 'Show full description' : 'Hide full description'}
+ </Typography>
+ ) : (
+ <Typography
+ className={classes.noDescription}
+ data-cy='no-description'
+ >
+ no description available
+ </Typography>
+ )}
+ </section>
+ <Tooltip
+ title='More options'
+ disableFocusListener
>
- <MoreVerticalIcon />
- </IconButton>
- </Tooltip>
+ <IconButton
+ aria-label='More options'
+ onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+ >
+ <MoreVerticalIcon data-cy='kebab-icon' />
+ </IconButton>
+ </Tooltip>
+ </section>
}
/>
- <CardContent className={classes.cardContent}>
- <section className={classes.subHeader}>
- <section className={classes.chipSection}>
+ {typeof currentResource.properties === 'object' && Object.keys(currentResource.properties).length > 0 && (
+ <CardContent className={classes.cardContent}>
<Typography component='div'>
- {typeof currentResource.properties === 'object' &&
- Object.keys(currentResource.properties).map((k) =>
- Array.isArray(currentResource.properties[k])
- ? currentResource.properties[k].map((v: string) => getPropertyChip(k, v, undefined, classes.tag))
- : getPropertyChip(k, currentResource.properties[k], undefined, classes.tag)
- )}
+ {Object.keys(currentResource.properties).map((k) =>
+ Array.isArray(currentResource.properties[k])
+ ? currentResource.properties[k].map((v: string) => getPropertyChip(k, v, undefined, classes.tag))
+ : getPropertyChip(k, currentResource.properties[k], undefined, classes.tag)
+ )}
</Typography>
- </section>
- <section className={classes.descriptionLabel}>
- {description ? (
- <Typography
- className={classes.showMore}
- onClick={() => toggleDescription(currentResource)}
- >
- Show full description
- </Typography>
- ) : (
- <Typography className={classes.noDescription}>no description available</Typography>
- )}
- </section>
- </section>
- <section>
- {showDescription &&
- <Typography className={classes.description}>
- {currentResource.description}
- </Typography>}
+ </CardContent>
+ )}
+ <Collapse
+ in={showDescription}
+ timeout='auto'
+ >
+ <section onClick={(ev) => ev.stopPropagation()}>
+ <Typography
+ className={classes.description}
+ data-cy='project-description'
+ >
+ {description}
+ </Typography>
</section>
- </CardContent>
+ </Collapse>
</Card>
);
};