X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/da1cb890801ac2f58c0a6f8815ebb711bcb0f3c9..6f80d59b4d111454ce848f18c93aec8a891ff908:/services/workbench2/src/views-components/project-details-card/project-details-card.tsx diff --git a/services/workbench2/src/views-components/project-details-card/project-details-card.tsx b/services/workbench2/src/views-components/project-details-card/project-details-card.tsx index 8e6ef5b92c..49884e45de 100644 --- a/services/workbench2/src/views-components/project-details-card/project-details-card.tsx +++ b/services/workbench2/src/views-components/project-details-card/project-details-card.tsx @@ -3,143 +3,199 @@ // 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, Grid } from '@material-ui/core'; import { ArvadosTheme } from 'common/custom-theme'; import { RootState } from 'store/store'; import { connect } from 'react-redux'; import { getResource } from 'store/resources/resources'; -import { MultiselectToolbar } from 'components/multiselect-toolbar/MultiselectToolbar'; -import { RichTextEditorLink } from 'components/rich-text-editor-link/rich-text-editor-link'; import { getPropertyChip } from '../resource-properties-form/property-chip'; import { ProjectResource } from 'models/project'; import { ResourceKind } from 'models/resource'; import { UserResource } from 'models/user'; -import { UserResourceAccountStatus, FrozenProject } from 'views-components/data-explorer/renderers'; +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 { Resource } from 'models/resource'; +import { Dispatch } from 'redux'; +import { loadDetailsPanel } from 'store/details-panel/details-panel-action'; +import { ExpandChevronRight } from 'components/expand-chevron-right/expand-chevron-right'; +import { MultiselectToolbar } from 'components/multiselect-toolbar/MultiselectToolbar'; +import { setSelectedResourceUuid } from 'store/selected-resource/selected-resource-actions'; +import { deselectAllOthers } from 'store/multiselect/multiselect-actions'; type CssRules = | 'root' - | 'cardheader' - | 'fadeout' - | 'showmore' - | 'nameContainer' - | 'activeIndicator' - | 'cardcontent' + | 'cardHeaderContainer' + | 'cardHeader' + | 'projectToolbar' + | 'descriptionToggle' + | 'showMore' + | 'noDescription' + | 'userNameContainer' + | 'cardContent' + | 'nameSection' | 'namePlate' | 'faveIcon' | 'frozenIcon' - | 'attributesection' - | 'attribute' - | 'chipsection' - | 'tag'; + | 'accountStatusSection' + | 'chipSection' + | 'tag' + | 'description'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { width: '100%', marginBottom: '1rem', + flex: '0 0 auto', + padding: 0, + minHeight: '3rem', }, - fadeout: { - maxWidth: '25rem', - minWdidth: '18rem', - height: '1.5rem', - overflow: 'hidden', - WebkitMaskImage: '-webkit-gradient(linear, left bottom, right bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)))', - }, - showmore: { - color: theme.palette.primary.main, + showMore: { cursor: 'pointer', - maxWidth: '10rem', }, - nameContainer: { + noDescription: { + color: theme.palette.grey['600'], + fontStyle: 'italic', + padding: '0 0 0.5rem 1rem', + marginTop: '-0.5rem', + }, + userNameContainer: { + display: 'flex', + alignItems: 'center', + minHeight: '2.7rem', + }, + cardHeaderContainer: { + width: '100%', display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + cardHeader: { + minWidth: '30rem', + padding: '0.2rem 0.4rem 0.2rem 1rem', }, - activeIndicator: { - margin: '0.3rem auto auto 1rem', + projectToolbar: { + //shows only the first 3 buttons + width: '12rem !important', }, - cardheader: { - paddingTop: '0.4rem', + descriptionToggle: { + display: 'flex', + flexDirection: 'row', + cursor: 'pointer', + paddingBottom: '0.5rem', }, - cardcontent: { + cardContent: { display: 'flex', flexDirection: 'column', - marginTop: '-1rem', + paddingTop: 0, + paddingLeft: '0.1rem', + }, + nameSection: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', }, namePlate: { display: 'flex', flexDirection: 'row', + alignItems: 'center', + margin: 0, + minHeight: '2.7rem', }, faveIcon: { fontSize: '0.8rem', - margin: 'auto 0 0.5rem 0.3rem', + margin: 'auto 0 1rem 0.3rem', color: theme.palette.text.primary, }, frozenIcon: { fontSize: '0.5rem', marginLeft: '0.3rem', - marginTop: '0.57rem', height: '1rem', color: theme.palette.text.primary, }, - attributesection: { + accountStatusSection: { display: 'flex', - flexWrap: 'wrap', - }, - attribute: { - marginBottom: '0.5rem', - marginRight: '1rem', - padding: '0.5rem', - borderRadius: '5px', + flexDirection: 'row', + alignItems: 'center', + paddingLeft: '1rem', }, - chipsection: { - display: 'flex', - flexWrap: 'wrap', + chipSection: { + marginBottom: '-2rem', }, tag: { - marginRight: '1rem', - marginTop: '0.5rem', + marginRight: '0.75rem', + marginBottom: '0.5rem', + }, + description: { + maxWidth: '95%', + marginTop: 0, }, }); -const mapStateToProps = (state: RootState) => { - const currentRoute = state.router.location?.pathname.split('/') || []; - const currentItemUuid = currentRoute[currentRoute.length - 1]; - 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 frozenByFullName = frozenByUser && 'fullName' in frozenByUser ? (frozenByUser as any).fullName : undefined; - return { +const mapStateToProps = ({ auth, selectedResourceUuid, resources, properties }: RootState) => { + const currentResource = getResource(properties.currentRouteUuid)(resources); + const frozenByUser = currentResource && getResource((currentResource as ProjectResource).frozenByUuid as string)(resources); + const frozenByFullName = frozenByUser && (frozenByUser as Resource & { fullName: string }).fullName; + const isSelected = selectedResourceUuid === properties.currentRouteUuid; + + return { + isAdmin: auth.user?.isAdmin, currentResource, frozenByFullName, + isSelected, }; }; +const mapDispatchToProps = (dispatch: Dispatch) => ({ + handleCardClick: (uuid: string) => { + dispatch(loadDetailsPanel(uuid)); + dispatch(setSelectedResourceUuid(uuid)); + dispatch(deselectAllOthers(uuid)); + }, + +}); + type DetailsCardProps = WithStyles & { currentResource: ProjectResource | UserResource; frozenByFullName?: string; + isAdmin: boolean; + isSelected: boolean; + handleCardClick: (resource: any) => void; }; type UserCardProps = WithStyles & { currentResource: UserResource; + isAdmin: boolean; + isSelected: boolean; + handleCardClick: (resource: any) => void; }; type ProjectCardProps = WithStyles & { currentResource: ProjectResource; frozenByFullName: string | undefined; + isAdmin: boolean; + isSelected: boolean; + handleCardClick: (resource: any) => void; }; -export const ProjectDetailsCard = connect(mapStateToProps)( +export const ProjectDetailsCard = connect( + mapStateToProps, + mapDispatchToProps +)( withStyles(styles)((props: DetailsCardProps) => { - const { classes, currentResource, frozenByFullName } = props; + const { classes, currentResource, frozenByFullName, handleCardClick, isAdmin, isSelected } = props; + if (!currentResource) { + return null; + } switch (currentResource.kind as string) { case ResourceKind.USER: return ( ); case ResourceKind.PROJECT: @@ -148,6 +204,9 @@ export const ProjectDetailsCard = connect(mapStateToProps)( classes={classes} currentResource={currentResource as ProjectResource} frozenByFullName={frozenByFullName} + isAdmin={isAdmin} + isSelected={isSelected} + handleCardClick={handleCardClick} /> ); default: @@ -156,96 +215,166 @@ export const ProjectDetailsCard = connect(mapStateToProps)( }) ); -const UserCard: React.FC = ({ classes, currentResource }) => { +const UserCard: React.FC = ({ classes, currentResource, handleCardClick, isSelected }) => { const { fullName, uuid } = currentResource as UserResource & { fullName: string }; return ( - - - - {fullName} - - {!currentResource.isActive && ( - - + handleCardClick(uuid)} + data-cy='user-details-card' + > + + + + {fullName} - )} - - } - action={} - /> +
+ {!currentResource.isActive && ( + + + + )} +
+ + } + /> + {isSelected && } +
); }; -const ProjectCard: React.FC = ({ classes, currentResource, frozenByFullName }) => { - const { name, uuid, description } = currentResource as ProjectResource; - +const ProjectCard: React.FC = ({ classes, currentResource, frozenByFullName, handleCardClick, isSelected }) => { + const { name, description, uuid } = currentResource as ProjectResource; + const [showDescription, setShowDescription] = React.useState(false); + const [showProperties, setShowProperties] = React.useState(false); + + const toggleDescription = () => { + setShowDescription(!showDescription); + }; + + const toggleProperties = () => { + setShowProperties(!showProperties); + }; + return ( - - - - {name} - - - - Project was frozen by {frozenByFullName}} + handleCardClick(uuid)} + data-cy='project-details-card' + > + + +
+ + {name} + + + + {!!frozenByFullName && ( + Project was frozen by {frozenByFullName}} + > + + + )} +
+ + } + /> + {isSelected && } +
+
ev.stopPropagation()}> + {description ? ( +
+ +
+ - - -
- } - subheader={ - description ? ( -
- {description.replace(/<[^>]*>/g, '').slice(0, 45)}... -
- -
+
- ) : ( - 'no description available' - ) - } - action={} - /> - -
- - {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) - )} +
+ ) : ( + + no description available -
- + )} + {typeof currentResource.properties === 'object' && Object.keys(currentResource.properties).length > 0 ? ( +
+ +
+ +
+ + + {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) + )} + + +
+
+
+
+ ) : null} +
); };