21651: Add virtual code snippet, implement in cmd card
authorStephen Smith <stephen@curii.com>
Fri, 5 Apr 2024 21:43:16 +0000 (17:43 -0400)
committerStephen Smith <stephen@curii.com>
Fri, 5 Apr 2024 21:43:16 +0000 (17:43 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

services/workbench2/src/components/code-snippet/code-snippet.tsx
services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx [new file with mode: 0644]
services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx [new file with mode: 0644]
services/workbench2/src/views/process-panel/process-cmd-card.tsx
services/workbench2/src/views/process-panel/process-panel-root.tsx

index 47d8fe1bf029bc5915a069c4a2c12dd08c8051b4..3be1e4fc71b4698fefc15ea48062b4b777aba563 100644 (file)
@@ -66,7 +66,7 @@ export const CodeSnippet = withStyles(styles)(connect(mapStateToProps)(
         </Typography>
 ));
 
-const renderLinks = (auth: FederationConfig, dispatch: Dispatch) => (text: string): JSX.Element => {
+export 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);
diff --git a/services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx b/services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx
new file mode 100644 (file)
index 0000000..bc43d5d
--- /dev/null
@@ -0,0 +1,75 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { StyleRulesCallback, WithStyles, Typography, withStyles } 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 } from 'routes/routes';
+import { renderLinks } from './code-snippet';
+import { FixedSizeList } from 'react-window';
+import AutoSizer from "react-virtualized-auto-sizer";
+
+type CssRules = 'root' | 'space' | 'content' ;
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        boxSizing: 'border-box',
+        height: '100%',
+        padding: theme.spacing.unit,
+    },
+    space: {
+        marginLeft: '15px',
+    },
+    content: {
+        maxHeight: '100%',
+        height: '100vh',
+    },
+});
+
+export interface CodeSnippetDataProps {
+    lines: string[];
+    lineTransformer?: (line: string) => string;
+    className?: string;
+    apiResponse?: boolean;
+    linked?: boolean;
+}
+
+interface CodeSnippetAuthProps {
+    auth: FederationConfig;
+}
+
+type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
+
+const mapStateToProps = (state: RootState): CodeSnippetAuthProps => ({
+    auth: state.auth,
+});
+
+export const VirtualCodeSnippet = withStyles(styles)(connect(mapStateToProps)(
+    ({ classes, lines, linked, className, apiResponse, dispatch, auth }: CodeSnippetProps & CodeSnippetAuthProps & DispatchProp) => {
+        const RenderRow = ({index, style}) => (
+            <span style={style}>{linked ? renderLinks(auth, dispatch)(lines[index]) : lines[index]}</span>
+        );
+
+        return <Typography
+            component="div"
+            className={classNames([classes.root, className])}>
+            <Typography className={classNames(classes.content, apiResponse ? classes.space : className)} component="pre">
+                <AutoSizer>
+                    {({ height, width }) =>
+                        <FixedSizeList
+                            height={height}
+                            width={width}
+                            itemSize={21}
+                            itemCount={lines.length}
+                        >
+                            {RenderRow}
+                        </FixedSizeList>
+                    }
+                </AutoSizer>
+            </Typography>
+        </Typography>;
+}));
diff --git a/services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx b/services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx
new file mode 100644 (file)
index 0000000..581f0f4
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
+import { VirtualCodeSnippet, CodeSnippetDataProps } from 'components/code-snippet/virtual-code-snippet';
+import grey from '@material-ui/core/colors/grey';
+import { themeOptions } from 'common/custom-theme';
+
+const theme = createMuiTheme(Object.assign({}, themeOptions, {
+    overrides: {
+        MuiTypography: {
+            body1: {
+                color: grey["900"]
+            },
+            root: {
+                backgroundColor: grey["200"]
+            }
+        }
+    },
+    typography: {
+        fontFamily: 'monospace',
+        useNextVariants: true,
+    }
+}));
+
+export const DefaultVirtualCodeSnippet = (props: CodeSnippetDataProps) =>
+    <MuiThemeProvider theme={theme}>
+        <VirtualCodeSnippet {...props} />
+    </MuiThemeProvider>;
index 478b0bc56fb76cc15169516d061f8094a90804ff..d7b47653e60d0fc113b2b7c4fff76a192a05ad9a 100644 (file)
@@ -18,7 +18,7 @@ import {
 import { ArvadosTheme } from 'common/custom-theme';
 import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
-import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
+import { DefaultVirtualCodeSnippet } from 'components/default-code-snippet/default-virtual-code-snippet';
 import { Process } from 'store/processes/process';
 import shellescape from 'shell-escape';
 import CopyToClipboard from 'react-copy-to-clipboard';
@@ -31,7 +31,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     header: {
         paddingTop: theme.spacing.unit,
-        paddingBottom: theme.spacing.unit,
+        paddingBottom: 0,
     },
     iconHeader: {
         fontSize: '1.875rem',
@@ -42,8 +42,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         paddingTop: theme.spacing.unit * 0.5
     },
     content: {
+        height: `calc(100% - ${theme.spacing.unit * 6}px)`,
         padding: theme.spacing.unit * 1.0,
-        paddingTop: theme.spacing.unit * 0.5,
+        paddingTop: 0,
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 1,
         }
@@ -52,7 +53,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         overflow: 'hidden',
         paddingTop: theme.spacing.unit * 0.5,
         color: theme.customs.colors.greyD,
-        fontSize: '1.875rem'  
+        fontSize: '1.875rem'
     },
 });
 
@@ -127,7 +128,7 @@ export const ProcessCmdCard = withStyles(styles)(
           }
         />
         <CardContent className={classes.content}>
-          <DefaultCodeSnippet lines={formattedCommand} linked />
+          <DefaultVirtualCodeSnippet lines={formattedCommand} linked />
         </CardContent>
       </Card>
     );
index 2a9b3882e86bec1592764ad46f837938c5eb3aa5..21f38b0938916730a76a66629db42d72d6a277a4 100644 (file)
@@ -198,6 +198,7 @@ export const ProcessPanelRoot = withStyles(styles)(
                 <MPVPanelContent
                     forwardProps
                     xs="auto"
+                    maxHeight={"50%"}
                     data-cy="process-cmd">
                     <ProcessCmdCard
                         onCopy={props.onCopyToClipboard}