21720: replaced theme.spacing.unit * x with theme.spacing(x) everywhere
[arvados.git] / services / workbench2 / src / components / code-snippet / code-snippet.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 { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { WithStyles, Typography, withStyles, Link } from '@material-ui/core';
8 import { ArvadosTheme } from 'common/custom-theme';
9 import classNames from 'classnames';
10 import { connect, DispatchProp } from 'react-redux';
11 import { RootState } from 'store/store';
12 import { FederationConfig, getNavUrl } from 'routes/routes';
13 import { Dispatch } from 'redux';
14 import { navigationNotAvailable } from 'store/navigation/navigation-action';
15
16 type CssRules = 'root' | 'inlineRoot' | 'space' | 'inline';
17
18 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
19     root: {
20         boxSizing: 'border-box',
21         overflow: 'auto',
22         padding: theme.spacing(1),
23     },
24     inlineRoot: {
25         padding: "3px",
26         display: "inline",
27     },
28     space: {
29         marginLeft: '15px',
30     },
31     inline: {
32         display: 'inline',
33     },
34 });
35
36 export interface CodeSnippetDataProps {
37     lines: string[];
38     className?: string;
39     apiResponse?: boolean;
40     linked?: boolean;
41     children?: JSX.Element;
42     inline?: boolean;
43 }
44
45 interface CodeSnippetAuthProps {
46     auth: FederationConfig;
47 }
48
49 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
50
51 const mapStateToProps = (state: RootState): CodeSnippetAuthProps => ({
52     auth: state.auth,
53 });
54
55 export const CodeSnippet = withStyles(styles)(connect(mapStateToProps)(
56     ({ classes, lines, linked, className, apiResponse, dispatch, auth, children, inline }: CodeSnippetProps & CodeSnippetAuthProps & DispatchProp) =>
57         <Typography
58             component="div"
59             className={classNames([classes.root, className, inline ? classes.inlineRoot : undefined])}>
60             <Typography className={apiResponse ? classes.space : classNames([className, inline ? classes.inline : undefined])} component="pre">
61                 {children}
62                 {linked ?
63                     lines.map((line, index) => <React.Fragment key={index}>{renderLinks(auth, dispatch)(line)}{`\n`}</React.Fragment>) :
64                     lines.join('\n')
65                 }
66             </Typography>
67         </Typography>
68 ));
69
70 export const renderLinks = (auth: FederationConfig, dispatch: Dispatch) => (text: string): JSX.Element => {
71     // Matches UUIDs & PDHs
72     const REGEX = /[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}|[0-9a-f]{32}\+\d+/g;
73     const links = text.match(REGEX);
74     if (!links) {
75         return <>{text}</>;
76     }
77     return <>
78         {text.split(REGEX).map((part, index) =>
79             <React.Fragment key={index}>
80                 {part}
81                 {links[index] &&
82                     <Link onClick={() => {
83                         const url = getNavUrl(links[index], auth)
84                         if (url) {
85                             window.open(`${window.location.origin}${url}`, '_blank', "noopener");
86                         } else {
87                             dispatch(navigationNotAvailable(links[index]));
88                         }
89                     }}
90                         style={{ cursor: 'pointer' }}>
91                         {links[index]}
92                     </Link>}
93             </React.Fragment>
94         )}
95     </>;
96 };