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