// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; 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 { getResource } from 'store/resources/resources'; 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 } from 'views-components/data-explorer/renderers'; import { FavoriteStar, PublicFavoriteStar } from 'views-components/favorite-star/favorite-star'; import { MoreVerticalIcon, FreezeIcon } from 'components/icon/icon'; import { Resource } from 'models/resource'; import { IconButton } from '@material-ui/core'; import { ContextMenuResource, openUserContextMenu } 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'; type CssRules = | 'root' | 'cardHeader' | 'descriptionLabel' | 'showMore' | 'noDescription' | 'nameContainer' | 'cardContent' | 'subHeader' | 'namePlate' | 'faveIcon' | 'frozenIcon' | 'contextMenuSection' | 'chipSection' | 'tag' | 'description'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { width: '100%', marginBottom: '1rem', flex: '0 0 auto', paddingTop: '0.2rem', }, showMore: { color: theme.palette.primary.main, cursor: 'pointer', }, noDescription: { color: theme.palette.grey['600'], fontStyle: 'italic', }, nameContainer: { display: 'flex', }, cardHeader: { paddingTop: '0.4rem', }, descriptionLabel: { paddingTop: '1rem', marginBottom: 0, minHeight: '2.5rem', marginRight: '0.8rem', }, cardContent: { display: 'flex', flexDirection: 'column', transition: 'height 0.3s ease', }, subHeader: { display: 'flex', flexDirection: 'row', justifyContent: 'space-between', marginTop: '-2rem', }, namePlate: { display: 'flex', flexDirection: 'row', }, faveIcon: { fontSize: '0.8rem', margin: 'auto 0 0.5rem 0.3rem', color: theme.palette.text.primary, }, frozenIcon: { fontSize: '0.5rem', marginLeft: '0.3rem', marginTop: '0.57rem', height: '1rem', color: theme.palette.text.primary, }, contextMenuSection: { display: 'flex', flexDirection: 'row', alignItems: 'center', marginTop: '0.6rem', }, chipSection: { display: 'flex', flexWrap: 'wrap', }, tag: { marginRight: '1rem', marginTop: '0.5rem', }, description: { marginTop: '1rem', }, }); 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; return { isAdmin: state.auth.user?.isAdmin, currentResource, frozenByFullName, }; }; const mapDispatchToProps = (dispatch: Dispatch) => ({ handleContextMenu: (event: React.MouseEvent, resource: any, isAdmin: boolean) => { event.stopPropagation(); // When viewing the contents of a filter group, all contents should be treated as read only. let readOnly = false; if (resource.groupClass === 'filter') { readOnly = true; } const menuKind = dispatch(resourceUuidToContextMenuKind(resource.uuid, readOnly)); if (menuKind === ContextMenuKind.ROOT_PROJECT) { dispatch(openUserContextMenu(event, resource as UserResource)); } else if (menuKind && resource) { dispatch( openContextMenu(event, { name: resource.name, uuid: resource.uuid, ownerUuid: resource.ownerUuid, isTrashed: 'isTrashed' in resource ? resource.isTrashed : false, kind: resource.kind, menuKind, isAdmin, isFrozen: !!resource.frozenByUuid, description: resource.description, storageClassesDesired: (resource as CollectionResource).storageClassesDesired, properties: 'properties' in resource ? resource.properties : {}, }) ); } }, }); type DetailsCardProps = WithStyles & { currentResource: ProjectResource | UserResource; frozenByFullName?: string; isAdmin: boolean; handleContextMenu: (event: React.MouseEvent, resource: ContextMenuResource, isAdmin: boolean) => void; }; type UserCardProps = WithStyles & { currentResource: UserResource; isAdmin: boolean; handleContextMenu: (event: React.MouseEvent, resource: ContextMenuResource, isAdmin: boolean) => void; }; type ProjectCardProps = WithStyles & { currentResource: ProjectResource; frozenByFullName: string | undefined; isAdmin: boolean; handleContextMenu: (event: React.MouseEvent, resource: ContextMenuResource, isAdmin: boolean) => void; }; export const ProjectDetailsCard = connect( mapStateToProps, mapDispatchToProps )( withStyles(styles)((props: DetailsCardProps) => { const { classes, currentResource, frozenByFullName, handleContextMenu, isAdmin } = props; switch (currentResource.kind as string) { case ResourceKind.USER: return ( handleContextMenu(ev, currentResource as any, isAdmin)} /> ); case ResourceKind.PROJECT: return ( handleContextMenu(ev, currentResource as any, isAdmin)} /> ); default: return null; } }) ); const UserCard: React.FC = ({ classes, currentResource, handleContextMenu, isAdmin }) => { const { fullName, uuid } = currentResource as UserResource & { fullName: string }; return ( {fullName} } action={
{!currentResource.isActive && ( )} handleContextMenu(ev, currentResource as any, isAdmin)} >
} />
); }; const ProjectCard: React.FC = ({ classes, currentResource, frozenByFullName, handleContextMenu, isAdmin }) => { const { name, description } = currentResource as ProjectResource; const [showDescription, setShowDescription] = React.useState(false); const toggleDescription = () => { setShowDescription(!showDescription); }; return ( {name} {!!frozenByFullName && ( Project was frozen by {frozenByFullName}} > )} } action={
handleContextMenu(ev, currentResource as any, isAdmin)} >
} />
{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) )}
{description ? ( {!showDescription ? "Show full description" : "Hide full description"} ) : ( no description available )}
{description}
); };