21621: Add copy button to virtual code snippet for io panel json tab
[arvados.git] / services / workbench2 / src / components / code-snippet / virtual-code-snippet.tsx
index 09db2c04261caeaf0ebd87e18203b56fbda5c4a8..e45289f02c33045e419c6bba9ed46dffaa3a969c 100644 (file)
@@ -3,20 +3,25 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { StyleRulesCallback, WithStyles, Typography, withStyles } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, Typography, withStyles, Tooltip, IconButton } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import classNames from 'classnames';
-import { connect, DispatchProp } from 'react-redux';
+import { connect } 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";
+import CopyResultToClipboard from 'components/copy-to-clipboard/copy-result-to-clipboard';
+import { CopyIcon } from 'components/icon/icon';
+import { SnackbarKind, snackbarActions } from 'store/snackbar/snackbar-actions';
+import { Dispatch } from "redux";
 
-type CssRules = 'root' | 'space' | 'content' ;
+type CssRules = 'root' | 'space' | 'content' | 'copyButton' ;
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
+        position: 'relative',
         boxSizing: 'border-box',
         height: '100%',
         padding: theme.spacing.unit,
@@ -28,6 +33,12 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         maxHeight: '100%',
         height: '100vh',
     },
+    copyButton: {
+        position: 'absolute',
+        top: '8px',
+        right: '8px',
+        zIndex: 100,
+    },
 });
 
 export interface CodeSnippetDataProps {
@@ -36,28 +47,65 @@ export interface CodeSnippetDataProps {
     className?: string;
     apiResponse?: boolean;
     linked?: boolean;
+    copyButton?: boolean;
+}
+
+export interface CodeSnippetActionProps {
+    renderLinks: (auth: FederationConfig) => (text: string) => JSX.Element;
+    onCopyToClipboard: () => void;
 }
 
 interface CodeSnippetAuthProps {
     auth: FederationConfig;
 }
 
-type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
+type CodeSnippetProps = CodeSnippetDataProps & CodeSnippetActionProps & WithStyles<CssRules>;
 
 const mapStateToProps = (state: RootState): CodeSnippetAuthProps => ({
     auth: state.auth,
 });
 
-export const VirtualCodeSnippet = withStyles(styles)(connect(mapStateToProps)(
-    ({ classes, lines, lineFormatter, linked, className, apiResponse, dispatch, auth }: CodeSnippetProps & CodeSnippetAuthProps & DispatchProp) => {
+const mapDispatchToProps = (dispatch: Dispatch): CodeSnippetActionProps => ({
+    renderLinks: (auth: FederationConfig) => renderLinks(auth, dispatch),
+    onCopyToClipboard: () => {
+        dispatch<any>(
+            snackbarActions.OPEN_SNACKBAR({
+                message: "Contents copied to clipboard",
+                hideDuration: 2000,
+                kind: SnackbarKind.SUCCESS,
+            })
+        );
+    },
+});
+
+export const VirtualCodeSnippet = withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(
+    ({ classes, lines, lineFormatter, linked, copyButton, renderLinks, onCopyToClipboard, className, apiResponse, auth }: CodeSnippetProps & CodeSnippetAuthProps) => {
         const RenderRow = ({index, style}) => {
             const lineContents = lineFormatter ? lineFormatter(lines, index) : lines[index];
-            return <span style={style}>{linked ? renderLinks(auth, dispatch)(lineContents) : lineContents}</span>
+            return <span style={style}>{linked ? renderLinks(auth)(lineContents) : lineContents}</span>
         };
 
+        const formatClipboardText = (lines: string[]) => () =>  {
+            return lines.join('\n');
+        };
+
+
+
         return <Typography
             component="div"
             className={classNames([classes.root, className])}>
+            {copyButton && <span className={classes.copyButton}>
+                <Tooltip title="Copy text to clipboard" disableFocusListener>
+                    <IconButton>
+                        <CopyResultToClipboard
+                            getText={formatClipboardText(lines)}
+                            onCopy={onCopyToClipboard}
+                        >
+                            <CopyIcon />
+                        </CopyResultToClipboard>
+                    </IconButton>
+                </Tooltip>
+            </span>}
             <Typography className={classNames(classes.content, apiResponse ? classes.space : className)} component="pre">
                 <AutoSizer>
                     {({ height, width }) =>