22235: removed unfreeze from manageable_project in toolbar
[arvados.git] / services / workbench2 / src / components / details-attribute / details-attribute.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { connect, DispatchProp } from 'react-redux';
7 import Typography from '@mui/material/Typography';
8 import { CustomStyleRulesCallback } from 'common/custom-theme';
9 import { WithStyles } from '@mui/styles';
10 import withStyles from '@mui/styles/withStyles';
11 import { Tooltip } from '@mui/material';
12 import { CopyIcon } from 'components/icon/icon';
13 import CopyToClipboard from 'react-copy-to-clipboard';
14 import { ArvadosTheme } from 'common/custom-theme';
15 import classnames from "classnames";
16 import { Link } from 'react-router-dom';
17 import { RootState } from "store/store";
18 import { FederationConfig, getNavUrl } from "routes/routes";
19 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
20
21 type CssRules = 'attribute' | 'label' | 'value' | 'lowercaseValue' | 'link' | 'copyIcon';
22
23 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
24     attribute: {
25         marginBottom: ".6 rem"
26     },
27     label: {
28         boxSizing: 'border-box',
29         color: theme.palette.grey["600"],
30         width: '100%',
31         marginTop: "0.4em",
32     },
33     value: {
34         boxSizing: 'border-box',
35         alignItems: 'flex-start'
36     },
37     lowercaseValue: {
38         textTransform: 'lowercase'
39     },
40     link: {
41         color: theme.palette.primary.main,
42         textDecoration: 'none',
43         overflowWrap: 'break-word',
44         cursor: 'pointer'
45     },
46     copyIcon: {
47         marginLeft: theme.spacing(1),
48         color: theme.palette.grey["600"],
49         cursor: 'pointer',
50         display: 'inline',
51         '& svg': {
52             fontSize: '1rem'
53         }
54     }
55 });
56
57 interface DetailsAttributeDataProps {
58     label: string;
59     classLabel?: string;
60     value?: React.ReactNode;
61     classValue?: string;
62     lowercaseValue?: boolean;
63     link?: string;
64     children?: React.ReactNode;
65     onValueClick?: () => void;
66     linkToUuid?: string;
67     copyValue?: string;
68     uuidEnhancer?: Function;
69 }
70
71 type DetailsAttributeProps = DetailsAttributeDataProps & WithStyles<CssRules> & FederationConfig & DispatchProp;
72
73 const mapStateToProps = ({ auth }: RootState): FederationConfig => ({
74     localCluster: auth.localCluster,
75     remoteHostsConfig: auth.remoteHostsConfig,
76     sessions: auth.sessions
77 });
78
79 export const DetailsAttribute = connect(mapStateToProps)(withStyles(styles)(
80     class extends React.Component<DetailsAttributeProps> {
81
82         onCopy = (message: string) => {
83             this.props.dispatch(snackbarActions.OPEN_SNACKBAR({
84                 message,
85                 hideDuration: 2000,
86                 kind: SnackbarKind.SUCCESS
87             }));
88         }
89
90         render() {
91             const { uuidEnhancer, link, value, classes, linkToUuid,
92                 localCluster, remoteHostsConfig, sessions } = this.props;
93             let valueNode: React.ReactNode;
94
95             if (linkToUuid) {
96                 const uuid = uuidEnhancer ? uuidEnhancer(linkToUuid) : linkToUuid;
97                 const linkUrl = getNavUrl(linkToUuid || "", { localCluster, remoteHostsConfig, sessions });
98                 if (linkUrl[0] === '/') {
99                     valueNode = <Link to={linkUrl} className={classes.link}>{uuid}</Link>;
100                 } else {
101                     valueNode = <a href={linkUrl} className={classes.link} target='_blank' rel="noopener noreferrer">{uuid}</a>;
102                 }
103             } else if (link) {
104                 valueNode = <a href={link} className={classes.link} target='_blank' rel="noopener noreferrer">{value}</a>;
105             } else {
106                 valueNode = value;
107             }
108
109             return <DetailsAttributeComponent {...this.props} value={valueNode} onCopy={this.onCopy} />;
110         }
111     }
112 ));
113
114 interface DetailsAttributeComponentProps {
115     value: React.ReactNode;
116     onCopy?: (msg: string) => void;
117 }
118
119 export const DetailsAttributeComponent = withStyles(styles)(
120     (props: DetailsAttributeDataProps & WithStyles<CssRules> & DetailsAttributeComponentProps) =>
121         <Typography component="div" className={props.classes.attribute} data-cy={`details-panel-${props.label.toLowerCase()}`}>
122             <Typography component="div" className={classnames([props.classes.label, props.classLabel])}>{props.label}</Typography>
123             <Typography
124                 onClick={props.onValueClick}
125                 component="div"
126                 data-cy="details-attribute-value"
127                 className={classnames([props.classes.value, props.classValue, { [props.classes.lowercaseValue]: props.lowercaseValue }])}>
128                 {props.value}
129                 {props.children}
130                 {(props.linkToUuid || props.copyValue) && props.onCopy && <Tooltip title="Copy link to clipboard">
131                     <span className={props.classes.copyIcon}>
132                         <CopyToClipboard text={props.linkToUuid || props.copyValue || ""} onCopy={() => props.onCopy!("Copied")}>
133                             <CopyIcon />
134                         </CopyToClipboard>
135                     </span>
136                 </Tooltip>}
137             </Typography>
138         </Typography>);