21224: css tweaks Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox@curii.com>
[arvados.git] / services / workbench2 / src / views-components / project-details-card / project-details-card.tsx
index 7e78f407b5644dec5d91916bae3eb136d308df49..7307af67aea6d236c932b98a6e765441313db37e 100644 (file)
@@ -3,8 +3,7 @@
 // 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';
@@ -15,31 +14,30 @@ import { ResourceKind } from 'models/resource';
 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';
 
@@ -48,7 +46,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         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,
@@ -58,31 +60,25 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         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',
@@ -92,7 +88,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     frozenIcon: {
         fontSize: '0.5rem',
         marginLeft: '0.3rem',
-        marginTop: '0.57rem',
+        marginTop: '0.1rem',
         height: '1rem',
         color: theme.palette.text.primary,
     },
@@ -100,11 +96,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         display: 'flex',
         flexDirection: 'row',
         alignItems: 'center',
-        marginTop: '0.6rem',
-    },
-    chipSection: {
-        display: 'flex',
-        flexWrap: 'wrap',
+        paddingTop: '0.25rem',
     },
     tag: {
         marginRight: '1rem',
@@ -121,15 +113,20 @@ const mapStateToProps = (state: RootState) => {
     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.
@@ -137,10 +134,11 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
         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,
@@ -164,20 +162,26 @@ type DetailsCardProps = WithStyles<CssRules> & {
     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(
@@ -185,7 +189,10 @@ 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 (
@@ -193,7 +200,9 @@ export const ProjectDetailsCard = connect(
                         classes={classes}
                         currentResource={currentResource as UserResource}
                         isAdmin={isAdmin}
+                        isSelected={isSelected}
                         handleContextMenu={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+                        handleCardClick={handleCardClick}
                     />
                 );
             case ResourceKind.PROJECT:
@@ -203,7 +212,9 @@ export const ProjectDetailsCard = connect(
                         currentResource={currentResource as ProjectResource}
                         frozenByFullName={frozenByFullName}
                         isAdmin={isAdmin}
+                        isSelected={isSelected}
                         handleContextMenu={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
+                        handleCardClick={handleCardClick}
                     />
                 );
             default:
@@ -212,15 +223,19 @@ export const ProjectDetailsCard = connect(
     })
 );
 
-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'
@@ -242,6 +257,7 @@ const UserCard: React.FC<UserCardProps> = ({ classes, currentResource, handleCon
                         >
                             <IconButton
                                 aria-label='More options'
+                                data-cy='kebab-icon'
                                 onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
                             >
                                 <MoreVerticalIcon />
@@ -254,17 +270,20 @@ const UserCard: React.FC<UserCardProps> = ({ classes, currentResource, handleCon
     );
 };
 
-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={
@@ -287,6 +306,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, fro
                         {!!frozenByFullName && (
                             <Tooltip
                                 className={classes.frozenIcon}
+                                disableFocusListener
                                 title={<span>Project was frozen by {frozenByFullName}</span>}
                             >
                                 <FreezeIcon style={{ fontSize: 'inherit' }} />
@@ -295,51 +315,66 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, fro
                     </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>
     );
 };