16070: Add optional linking to code snippet component. Base styles off of default...
[arvados-workbench2.git] / src / components / code-snippet / code-snippet.tsx
index eb0e709a9b0dfa572860e15b796b436e065b41e3..83c378b899ec11a884cb6d213b932423a14ba0aa 100644 (file)
@@ -2,36 +2,84 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from 'react';
-import { StyleRulesCallback, WithStyles, Typography, withStyles, Theme } from '@material-ui/core';
-import { ArvadosTheme } from '~/common/custom-theme';
+import React from 'react';
+import { StyleRulesCallback, WithStyles, Typography, withStyles, Link } from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import classNames from 'classnames';
+import { connect, DispatchProp } from 'react-redux';
+import { RootState } from 'store/store';
+import { FederationConfig, getNavUrl } from 'routes/routes';
+import { Dispatch } from 'redux';
+import { navigationNotAvailable } from 'store/navigation/navigation-action';
 
-type CssRules = 'root';
+type CssRules = 'root' | 'space';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
         boxSizing: 'border-box',
-        width: '100%',
-        height: 'auto',
-        maxHeight: '550px',
         overflow: 'auto',
         padding: theme.spacing.unit
+    },
+    space: {
+        marginLeft: '15px'
     }
 });
 
 export interface CodeSnippetDataProps {
     lines: string[];
+    className?: string;
+    apiResponse?: boolean;
+    linked?: boolean;
+}
+
+interface CodeSnippetAuthProps {
+    auth: FederationConfig;
 }
 
 type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
 
-export const CodeSnippet = withStyles(styles)(
-    ({ classes, lines }: CodeSnippetProps) =>
-        <Typography component="div" className={classes.root}>
-            {
-                lines.map((line: string, index: number) => {
-                    return <Typography key={index} component="pre">{line}</Typography>;
-                })
-            }
+const mapStateToProps = (state: RootState): CodeSnippetAuthProps => ({
+    auth: state.auth,
+});
+
+export const CodeSnippet = withStyles(styles)(connect(mapStateToProps)(
+    ({ classes, lines, linked, className, apiResponse, dispatch, auth }: CodeSnippetProps & CodeSnippetAuthProps & DispatchProp) =>
+        <Typography
+        component="div"
+        className={classNames(classes.root, className)}>
+            <Typography className={apiResponse ? classes.space : className} component="pre">
+                {linked ?
+                    lines.map((line, index) => <React.Fragment key={index}>{renderLinks(auth, dispatch)(line)}{`\n`}</React.Fragment>) :
+                    lines.join('\n')
+                }
+            </Typography>
         </Typography>
-    );
\ No newline at end of file
+    ));
+
+const renderLinks = (auth: FederationConfig, dispatch: Dispatch) => (text: string): JSX.Element => {
+    // Matches UUIDs & PDHs
+    const REGEX = /[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}|[0-9a-f]{32}\+\d+/g;
+    const links = text.match(REGEX);
+    if (!links) {
+        return <>{text}</>;
+    }
+    return <>
+        {text.split(REGEX).map((part, index) =>
+            <React.Fragment key={index}>
+                {part}
+                {links[index] &&
+                <Link onClick={() => {
+                    const url = getNavUrl(links[index], auth)
+                    if (url) {
+                        window.open(`${window.location.origin}${url}`, '_blank');
+                    } else {
+                        dispatch(navigationNotAvailable(links[index]));
+                    }
+                }}
+                    style={ {cursor: 'pointer'} }>
+                    {links[index]}
+                </Link>}
+            </React.Fragment>
+        )}
+    </>;
+  };