1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React from 'react';
6 import { Card, CardHeader, WithStyles, withStyles, Typography, CardContent, Tooltip } from '@material-ui/core';
7 import { StyleRulesCallback } from '@material-ui/core';
8 import { ArvadosTheme } from 'common/custom-theme';
9 import { RootState } from 'store/store';
10 import { connect } from 'react-redux';
11 import { getResource } from 'store/resources/resources';
12 import { MultiselectToolbar } from 'components/multiselect-toolbar/MultiselectToolbar';
13 import { getPropertyChip } from '../resource-properties-form/property-chip';
14 import { ProjectResource } from 'models/project';
15 import { ResourceKind } from 'models/resource';
16 import { UserResource } from 'models/user';
17 import { UserResourceAccountStatus } from 'views-components/data-explorer/renderers';
18 import { FavoriteStar, PublicFavoriteStar } from 'views-components/favorite-star/favorite-star';
19 import { FreezeIcon } from 'components/icon/icon';
20 import { Resource } from 'models/resource';
21 import { MoreVerticalIcon } from 'components/icon/icon';
22 import { IconButton } from '@material-ui/core';
23 import { ContextMenuResource } from 'store/context-menu/context-menu-actions';
24 import { resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
25 import { openContextMenu } from 'store/context-menu/context-menu-actions';
26 import { CollectionResource } from 'models/collection';
27 import { RichTextEditorLink } from 'components/rich-text-editor-link/rich-text-editor-link';
45 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
55 WebkitMaskImage: '-webkit-gradient(linear, left bottom, right bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)))',
58 color: theme.palette.primary.main,
66 margin: '0.3rem auto auto 1rem',
73 flexDirection: 'column',
82 margin: 'auto 0 0.5rem 0.3rem',
83 color: theme.palette.text.primary,
90 color: theme.palette.text.primary,
97 marginBottom: '0.5rem',
112 const mapStateToProps = (state: RootState) => {
113 const currentRoute = state.router.location?.pathname.split('/') || [];
114 const currentItemUuid = currentRoute[currentRoute.length - 1];
115 const currentResource = getResource(currentItemUuid)(state.resources);
116 const frozenByUser = currentResource && getResource((currentResource as ProjectResource).frozenByUuid as string)(state.resources);
117 const frozenByFullName = frozenByUser && (frozenByUser as Resource & { fullName: string }).fullName;
120 isAdmin: state.auth.user?.isAdmin,
126 const mapDispatchToProps = (dispatch: any) => ({
127 handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: any, isAdmin: boolean) => {
128 // When viewing the contents of a filter group, all contents should be treated as read only.
129 let readOnly = false;
130 if (resource.groupClass === 'filter') {
134 const menuKind = dispatch(resourceUuidToContextMenuKind(resource.uuid, readOnly));
135 if (menuKind && resource) {
137 openContextMenu(event, {
140 ownerUuid: resource.ownerUuid,
141 isTrashed: 'isTrashed' in resource ? resource.isTrashed : false,
145 isFrozen: !!resource.frozenByUuid,
146 description: resource.description,
147 storageClassesDesired: (resource as CollectionResource).storageClassesDesired,
148 properties: 'properties' in resource ? resource.properties : {},
156 type DetailsCardProps = WithStyles<CssRules> & {
157 currentResource: ProjectResource | UserResource;
158 frozenByFullName?: string;
160 handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource, isAdmin: boolean) => void;
163 type UserCardProps = WithStyles<CssRules> & {
164 currentResource: UserResource;
167 type ProjectCardProps = WithStyles<CssRules> & {
168 currentResource: ProjectResource;
169 frozenByFullName: string | undefined;
171 handleContextMenu: (event: React.MouseEvent<HTMLElement>, resource: ContextMenuResource, isAdmin: boolean) => void;
174 export const ProjectDetailsCard = connect(
178 withStyles(styles)((props: DetailsCardProps) => {
179 const { classes, currentResource, frozenByFullName, handleContextMenu, isAdmin } = props;
180 switch (currentResource.kind as string) {
181 case ResourceKind.USER:
185 currentResource={currentResource as UserResource}
188 case ResourceKind.PROJECT:
192 currentResource={currentResource as ProjectResource}
193 frozenByFullName={frozenByFullName}
195 handleContextMenu={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
204 const UserCard: React.FC<UserCardProps> = ({ classes, currentResource }) => {
205 const { fullName, uuid } = currentResource as UserResource & { fullName: string };
208 <Card className={classes.root}>
210 className={classes.cardheader}
212 <section className={classes.nameContainer}>
219 {!currentResource.isActive && (
220 <Typography className={classes.activeIndicator}>
221 <UserResourceAccountStatus uuid={uuid} />
226 action={<MultiselectToolbar inputSelectedUuid={uuid} />}
232 const ProjectCard: React.FC<ProjectCardProps> = ({ classes, currentResource, frozenByFullName, handleContextMenu, isAdmin }) => {
233 const { name, uuid, description } = currentResource as ProjectResource;
236 <Card className={classes.root}>
238 className={classes.cardheader}
241 <section className={classes.namePlate}>
245 style={{ marginRight: '1rem' }}
250 className={classes.faveIcon}
251 resourceUuid={currentResource.uuid}
254 className={classes.faveIcon}
255 resourceUuid={currentResource.uuid}
257 {!!frozenByFullName && <Tooltip
258 className={classes.frozenIcon}
259 title={<span>Project was frozen by {frozenByFullName}</span>}
261 <FreezeIcon style={{ fontSize: 'inherit' }} />
264 <section className={classes.chipsection}>
265 <Typography component='div'>
266 {typeof currentResource.properties === 'object' &&
267 Object.keys(currentResource.properties).map((k) =>
268 Array.isArray(currentResource.properties[k])
269 ? currentResource.properties[k].map((v: string) => getPropertyChip(k, v, undefined, classes.tag))
270 : getPropertyChip(k, currentResource.properties[k], undefined, classes.tag)
283 aria-label='More options'
284 onClick={(ev) => handleContextMenu(ev, currentResource as any, isAdmin)}
290 <CardContent className={classes.cardcontent}>
293 {/* <Typography className={classes.fadeout}>{description.replace(/<[^>]*>/g, '').slice(0, 45)}...</Typography> */}
294 <div className={classes.showmore}>
296 title={`Description of ${name}`}
297 content={description}
298 label='Show full description'