16070: Add optional linking to code snippet component. Base styles off of default...
authorStephen Smith <stephen@curii.com>
Mon, 25 Jul 2022 22:54:37 +0000 (18:54 -0400)
committerStephen Smith <stephen@curii.com>
Tue, 26 Jul 2022 01:54:37 +0000 (21:54 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

src/components/code-snippet/code-snippet.tsx
src/components/default-code-snippet/default-code-snippet.tsx
src/views/process-panel/process-cmd-card.tsx

index f156e3e8e6e7ad26258662617f6ce3aafd60b960..83c378b899ec11a884cb6d213b932423a14ba0aa 100644 (file)
@@ -3,9 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { StyleRulesCallback, WithStyles, Typography, withStyles } from '@material-ui/core';
+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' | 'space';
 
@@ -24,17 +29,57 @@ 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, className, apiResponse }: CodeSnippetProps) =>
+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">
-                {lines.join('\n')}
+                {linked ?
+                    lines.map((line, index) => <React.Fragment key={index}>{renderLinks(auth, dispatch)(line)}{`\n`}</React.Fragment>) :
+                    lines.join('\n')
+                }
             </Typography>
         </Typography>
-    );
+    ));
+
+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>
+        )}
+    </>;
+  };
index 7ba97db49fbef65b723c51327ac47e94fcf6e7fe..bdcfc10f644b8c4f7ff38bed436bc521333d7109 100644 (file)
@@ -6,8 +6,9 @@ import React from 'react';
 import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
 import { CodeSnippet, CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
 import grey from '@material-ui/core/colors/grey';
+import { themeOptions } from 'common/custom-theme';
 
-const theme = createMuiTheme({
+const theme = createMuiTheme(Object.assign({}, themeOptions, {
     overrides: {
         MuiTypography: {
             body1: {
@@ -22,9 +23,9 @@ const theme = createMuiTheme({
         fontFamily: 'monospace',
         useNextVariants: true,
     }
-});
+}));
 
-export const DefaultCodeSnippet = (props: CodeSnippetDataProps) => 
+export const DefaultCodeSnippet = (props: CodeSnippetDataProps) =>
     <MuiThemeProvider theme={theme}>
         <CodeSnippet {...props} />
-    </MuiThemeProvider>;
\ No newline at end of file
+    </MuiThemeProvider>;
index 36a0128b0bc6d592e11ba5fe052575da99c849e2..4143501e23f495e99d97f1557f0ae48de5e4a689 100644 (file)
@@ -54,7 +54,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-export interface ProcessCmdCardDataProps {
+interface ProcessCmdCardDataProps {
   process: Process;
   onCopy: (text: string) => void;
 }
@@ -125,7 +125,7 @@ export const ProcessCmdCard = withStyles(styles)(
           }
         />
         <CardContent className={classes.content}>
-          <DefaultCodeSnippet lines={formattedCommand} />
+          <DefaultCodeSnippet lines={formattedCommand} linked />
         </CardContent>
       </Card>
     );