Browse Source

Merge pull request #213 from Tumao727/feature/code-mode

Create index add code mode
ryjiang 3 years ago
parent
commit
cf78d86147
48 changed files with 788 additions and 338 deletions
  1. 1 1
      client/.prettierrc
  2. 6 2
      client/package.json
  3. 4 0
      client/public/index.html
  4. 0 38
      client/src/components/__test__/copy/Copy.spec.tsx
  5. 10 11
      client/src/components/advancedSearch/CopyButton.tsx
  6. 1 0
      client/src/components/advancedSearch/Types.ts
  7. 1 1
      client/src/components/advancedSearch/index.tsx
  8. 63 0
      client/src/components/code/CodeBlock.tsx
  9. 113 0
      client/src/components/code/CodeView.tsx
  10. 20 0
      client/src/components/code/Types.ts
  11. 0 55
      client/src/components/copy/Copy.tsx
  12. 27 7
      client/src/components/customButton/CustomIconButton.tsx
  13. 9 6
      client/src/components/customDialog/CustomDialog.tsx
  14. 1 4
      client/src/components/customDialog/CustomDialogTitle.tsx
  15. 1 0
      client/src/components/customDialog/DeleteDialogTemplate.tsx
  16. 78 28
      client/src/components/customDialog/DialogTemplate.tsx
  17. 4 0
      client/src/components/customDialog/Types.ts
  18. 1 1
      client/src/components/customProgress/CustomLinearProgress.tsx
  19. 33 0
      client/src/components/customSwitch/CustomSwitch.tsx
  20. 3 0
      client/src/components/customSwitch/Types.ts
  21. 4 2
      client/src/components/customTabList/CustomTabList.tsx
  22. 1 0
      client/src/components/customTabList/Types.ts
  23. 5 2
      client/src/components/grid/Grid.tsx
  24. 13 2
      client/src/components/grid/Table.tsx
  25. 4 2
      client/src/components/grid/TablePaginationActions.tsx
  26. 2 1
      client/src/components/grid/ToolBar.tsx
  27. 2 2
      client/src/components/insert/Import.tsx
  28. 0 105
      client/src/components/layout/GlobalToolbar.tsx
  29. 2 2
      client/src/components/layout/Header.tsx
  30. 24 30
      client/src/components/menu/NavMenu.tsx
  31. 2 2
      client/src/components/status/Status.tsx
  32. 1 3
      client/src/context/Auth.tsx
  33. 6 1
      client/src/i18n/cn/common.ts
  34. 6 1
      client/src/i18n/en/common.ts
  35. 6 1
      client/src/index.css
  36. 1 1
      client/src/pages/connect/Connect.tsx
  37. 5 1
      client/src/pages/overview/collectionCard/CollectionCard.tsx
  38. 1 1
      client/src/pages/overview/statisticsCard/StatisticsCard.tsx
  39. 60 20
      client/src/pages/schema/Create.tsx
  40. 23 4
      client/src/pages/schema/IndexTypeElement.tsx
  41. 1 1
      client/src/pages/schema/Schema.tsx
  42. 14 0
      client/src/styles/common.css
  43. 5 0
      client/src/styles/theme.ts
  44. 14 0
      client/src/utils/Format.ts
  45. 16 0
      client/src/utils/code/Js.ts
  46. 27 0
      client/src/utils/code/Py.ts
  47. 10 0
      client/src/utils/code/Types.ts
  48. 157 0
      client/yarn.lock

+ 1 - 1
.prettierrc → client/.prettierrc

@@ -7,4 +7,4 @@
   "trailingComma": "es5",
   "trailingComma": "es5",
   "bracketSpacing": true,
   "bracketSpacing": true,
   "arrowParens": "avoid"
   "arrowParens": "avoid"
-}
+}

+ 6 - 2
client/package.json

@@ -19,6 +19,7 @@
     "@types/react-dom": "^17.0.0",
     "@types/react-dom": "^17.0.0",
     "@types/react-highlight-words": "^0.16.2",
     "@types/react-highlight-words": "^0.16.2",
     "@types/react-router-dom": "^5.1.7",
     "@types/react-router-dom": "^5.1.7",
+    "@types/react-syntax-highlighter": "^13.5.2",
     "axios": "^0.21.1",
     "axios": "^0.21.1",
     "dayjs": "^1.10.5",
     "dayjs": "^1.10.5",
     "i18next": "^20.3.1",
     "i18next": "^20.3.1",
@@ -30,6 +31,7 @@
     "react-i18next": "^11.10.0",
     "react-i18next": "^11.10.0",
     "react-router-dom": "^5.2.0",
     "react-router-dom": "^5.2.0",
     "react-scripts": "4.0.3",
     "react-scripts": "4.0.3",
+    "react-syntax-highlighter": "^15.4.4",
     "socket.io-client": "^4.1.3",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
     "typescript": "^4.1.2",
     "web-vitals": "^1.0.1"
     "web-vitals": "^1.0.1"
@@ -44,7 +46,8 @@
     "test:watch": "react-app-rewired test --watch",
     "test:watch": "react-app-rewired test --watch",
     "test:cov": "react-app-rewired test --watchAll=false --coverage",
     "test:cov": "react-app-rewired test --watchAll=false --coverage",
     "test:report": "react-app-rewired test --watchAll=false --coverage --coverageReporters='text-summary'",
     "test:report": "react-app-rewired test --watchAll=false --coverage --coverageReporters='text-summary'",
-    "eject": "react-app-rewired eject"
+    "eject": "react-app-rewired eject",
+    "format": "prettier --write '**/*.{ts,js,tsx,jsx,css}'"
   },
   },
   "eslintConfig": {
   "eslintConfig": {
     "extends": [
     "extends": [
@@ -65,6 +68,7 @@
     ]
     ]
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@testing-library/react-hooks": "^7.0.1"
+    "@testing-library/react-hooks": "^7.0.1",
+    "prettier": "2.3.2"
   }
   }
 }
 }

+ 4 - 0
client/public/index.html

@@ -19,6 +19,10 @@
       href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap"
       href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap"
       rel="stylesheet"
       rel="stylesheet"
     />
     />
+    <link
+      href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400&display=swap"
+      rel="stylesheet"
+    />
     <script src="%PUBLIC_URL%/env-config.js"></script>
     <script src="%PUBLIC_URL%/env-config.js"></script>
 
 
     <!--
     <!--

+ 0 - 38
client/src/components/__test__/copy/Copy.spec.tsx

@@ -1,38 +0,0 @@
-import { ReactNode } from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
-import Copy from '../../copy/Copy';
-let container: any = null;
-
-jest.mock('react-i18next', () => ({
-  useTranslation: () => ({
-    t: (key: any) => key,
-  }),
-}));
-
-jest.mock('@material-ui/core/Tooltip', () => {
-  return (props: { children: ReactNode }) => {
-    return <div id="tooltip">{props.children}</div>;
-  };
-});
-
-describe('Test Copy Component', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
-  it('Test props ', () => {
-    act(() => {
-      render(<Copy data={[]}></Copy>, container);
-    });
-
-    expect(document.querySelectorAll('button').length).toEqual(1);
-  });
-});

+ 10 - 11
client/src/components/advancedSearch/CopyButton.tsx

@@ -3,25 +3,24 @@ import { makeStyles, Theme, createStyles } from '@material-ui/core';
 import { CopyButtonProps } from './Types';
 import { CopyButtonProps } from './Types';
 import icons from '../icons/Icons';
 import icons from '../icons/Icons';
 import CustomIconButton from '../customButton/CustomIconButton';
 import CustomIconButton from '../customButton/CustomIconButton';
+import { useTranslation } from 'react-i18next';
 
 
 const CopyIcon = icons.copyExpression;
 const CopyIcon = icons.copyExpression;
 
 
 const CopyButton: FC<CopyButtonProps> = props => {
 const CopyButton: FC<CopyButtonProps> = props => {
-  const {
-    label = 'copy button',
-    icon,
-    className,
-    value = '',
-    ...others
-  } = props;
+  const { label, icon, className, value = '', ...others } = props;
   const classes = useStyles();
   const classes = useStyles();
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy');
   const [tooltipTitle, setTooltipTitle] = useState('Copy');
   const [tooltipTitle, setTooltipTitle] = useState('Copy');
 
 
-  const handleClick = (v: string) => {
-    setTooltipTitle('Copied!');
+  const handleClick = (event: React.MouseEvent<HTMLElement>, v: string) => {
+    event.stopPropagation();
+
+    setTooltipTitle(copyTrans.copied);
     navigator.clipboard.writeText(v);
     navigator.clipboard.writeText(v);
     setTimeout(() => {
     setTimeout(() => {
-      setTooltipTitle('Copy');
+      setTooltipTitle(copyTrans.copy);
     }, 1000);
     }, 1000);
   };
   };
 
 
@@ -30,7 +29,7 @@ const CopyButton: FC<CopyButtonProps> = props => {
       tooltip={tooltipTitle}
       tooltip={tooltipTitle}
       aria-label={label}
       aria-label={label}
       className={`${classes.button} ${className}`}
       className={`${classes.button} ${className}`}
-      onClick={() => handleClick(value || '')}
+      onClick={event => handleClick(event, value || '')}
       {...others}
       {...others}
     >
     >
       {icon || <CopyIcon style={{ color: 'transparent' }} />}
       {icon || <CopyIcon style={{ color: 'transparent' }} />}

+ 1 - 0
client/src/components/advancedSearch/Types.ts

@@ -44,6 +44,7 @@ export interface AddConditionProps {
 export interface CopyButtonProps {
 export interface CopyButtonProps {
   className?: string;
   className?: string;
   icon?: any;
   icon?: any;
+  // needed for accessibility, will not show on page
   label: string;
   label: string;
   value: string;
   value: string;
   others?: any;
   others?: any;

+ 1 - 1
client/src/components/advancedSearch/index.tsx

@@ -1,3 +1,3 @@
 import Filter from './Filter';
 import Filter from './Filter';
 
 
-export default Filter;
+export default Filter;

+ 63 - 0
client/src/components/code/CodeBlock.tsx

@@ -0,0 +1,63 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import CopyButton from '../advancedSearch/CopyButton';
+import SyntaxHighlighter from 'react-syntax-highlighter';
+import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
+import { FC } from 'react';
+import { CodeBlockProps } from './Types';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    position: 'relative',
+    padding: theme.spacing(3),
+    borderRadius: 8,
+    backgroundColor: '#fff',
+    color: '#454545',
+  },
+  block: {
+    margin: 0,
+  },
+  copy: {
+    position: 'absolute',
+    top: theme.spacing(2),
+    right: theme.spacing(2),
+  },
+}));
+
+const CodeStyle = {
+  backgroundColor: '#fff',
+  padding: 0,
+  margin: 0,
+  marginRight: 32,
+  fontSize: 14,
+};
+
+const CodeBlock: FC<CodeBlockProps> = ({
+  code,
+  language,
+  wrapperClass = '',
+}) => {
+  const classes = getStyles();
+
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy');
+
+  return (
+    <div className={`${classes.wrapper} ${wrapperClass}`}>
+      <CopyButton
+        className={classes.copy}
+        label={copyTrans.label}
+        value={code}
+      />
+      <SyntaxHighlighter
+        language={language}
+        style={docco}
+        customStyle={CodeStyle}
+      >
+        {code}
+      </SyntaxHighlighter>
+    </div>
+  );
+};
+
+export default CodeBlock;

+ 113 - 0
client/src/components/code/CodeView.tsx

@@ -0,0 +1,113 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomTabList from '../customTabList/CustomTabList';
+import { ITab } from '../customTabList/Types';
+import CodeBlock from './CodeBlock';
+import { CodeViewProps } from './Types';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    boxSizing: 'border-box',
+    width: '100%',
+
+    padding: theme.spacing(4),
+    backgroundColor: theme.palette.milvusDark.main,
+    borderRadius: 8,
+
+    color: '#fff',
+  },
+  title: {
+    marginBottom: theme.spacing(2),
+  },
+
+  // override tab list style
+  tabs: {
+    minHeight: 0,
+
+    '& .MuiTab-wrapper': {
+      textTransform: 'uppercase',
+      fontWeight: 'bold',
+      color: '#fff',
+    },
+
+    '& .MuiTab-root': {
+      minHeight: 18,
+      marginRight: 0,
+    },
+
+    // disable Ripple Effect
+    '& .MuiTouchRipple-root': {
+      display: 'none',
+    },
+
+    '& .Mui-selected': {
+      '& .MuiTab-wrapper': {
+        color: theme.palette.primary.main,
+      },
+    },
+
+    '& .MuiTabs-indicator': {
+      display: 'flex',
+      justifyContent: 'center',
+
+      top: 32,
+      backgroundColor: 'transparent',
+
+      '& .tab-indicator': {
+        height: 1,
+        width: '100%',
+        maxWidth: 26,
+        backgroundColor: theme.palette.primary.main,
+      },
+    },
+
+    '& .MuiTabs-flexContainer': {
+      borderBottom: 'none',
+    },
+  },
+
+  block: {
+    /**
+     * container height minus:
+     * 1. CodeView padding top and bottom (32 * 2)
+     * 2. CodeBlock padding top and bottom (24 * 2)
+     * 3. title height and margin bottom (24 + 16)
+     * 4. tab title height and margin bottom (36 + 16)
+     */
+    height: (props: { height: number }) =>
+      props.height - 32 * 2 - 24 * 2 - (24 + 16) - (36 + 16),
+    overflowY: 'auto',
+  },
+}));
+
+const CodeView: FC<CodeViewProps> = ({
+  wrapperClass = '',
+  data,
+  height = 0,
+}) => {
+  const classes = getStyles({ height });
+  const { t: commonTrans } = useTranslation();
+
+  const tabs: ITab[] = data.map(item => ({
+    label: item.label,
+    component: (
+      <CodeBlock
+        wrapperClass={height !== 0 ? classes.block : ''}
+        language={item.language}
+        code={item.code}
+      />
+    ),
+  }));
+
+  return (
+    <section className={`${classes.wrapper} ${wrapperClass}`}>
+      <Typography variant="h5" className={classes.title}>
+        {commonTrans('code')}
+      </Typography>
+      <CustomTabList tabs={tabs} wrapperClass={classes.tabs} />
+    </section>
+  );
+};
+
+export default CodeView;

+ 20 - 0
client/src/components/code/Types.ts

@@ -0,0 +1,20 @@
+export interface CodeViewProps {
+  height?: number;
+  wrapperClass?: string;
+  data: CodeViewData[];
+}
+
+export enum CodeLanguageEnum {
+  javascript = 'javascript',
+  python = 'python',
+}
+
+export interface CodeBlockProps {
+  code: string;
+  language: CodeLanguageEnum;
+  wrapperClass?: string;
+}
+
+export interface CodeViewData extends CodeBlockProps {
+  label: string;
+}

+ 0 - 55
client/src/components/copy/Copy.tsx

@@ -1,55 +0,0 @@
-import { IconButton, makeStyles } from '@material-ui/core';
-import { useState } from 'react';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { copyToCommand } from '../../utils/Common';
-import CustomToolTip from '../customToolTip/CustomToolTip';
-import Icons from '../icons/Icons';
-
-const useStyles = makeStyles(theme => ({
-  copy: {
-    cursor: 'pointer',
-    '& svg': {
-      fontSize: '12.8px',
-    },
-  },
-}));
-
-let timer: null | NodeJS.Timeout = null;
-const Copy = (props: { data: any }) => {
-  const classes = useStyles();
-  const { data } = props;
-  const { t } = useTranslation();
-  const copyTrans = t('copy') as any;
-  const [title, setTitle] = useState(copyTrans.copy);
-
-  const handleCopy = (
-    e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
-    data: string
-  ) => {
-    if (timer) {
-      clearTimeout(timer);
-    }
-    e.stopPropagation();
-
-    const cb = () => {
-      setTitle(copyTrans.copied);
-      setTimeout(() => {
-        setTitle(copyTrans.copy);
-      }, 1100);
-    };
-    timer = setTimeout(() => {
-      copyToCommand(data, '', cb);
-    }, 200);
-  };
-
-  return (
-    <CustomToolTip leaveDelay={900} title={title} placement="top">
-      <IconButton className={classes.copy} onClick={e => handleCopy(e, data)}>
-        {Icons.copy()}
-      </IconButton>
-    </CustomToolTip>
-  );
-};
-
-export default Copy;

+ 27 - 7
client/src/components/customButton/CustomIconButton.tsx

@@ -1,20 +1,40 @@
-import { IconButtonProps, Tooltip, IconButton } from '@material-ui/core';
+import {
+  IconButtonProps,
+  Tooltip,
+  IconButton,
+  makeStyles,
+  Theme,
+} from '@material-ui/core';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'inline-block',
+  },
+  iconBtn: {
+    padding: theme.spacing(1),
+  },
+}));
 
 
 const CustomIconButton = (props: IconButtonProps & { tooltip?: string }) => {
 const CustomIconButton = (props: IconButtonProps & { tooltip?: string }) => {
-  const { tooltip, ...otherProps } = props;
+  const { tooltip, className, ...otherProps } = props;
+  const classes = getStyles();
 
 
   return (
   return (
-    <>
+    <div className={`${classes.wrapper} ${className}`}>
       {tooltip ? (
       {tooltip ? (
-        <Tooltip title={tooltip}>
+        <Tooltip title={tooltip} arrow>
           <span>
           <span>
-            <IconButton {...otherProps}>{props.children}</IconButton>
+            <IconButton classes={{ root: classes.iconBtn }} {...otherProps}>
+              {props.children}
+            </IconButton>
           </span>
           </span>
         </Tooltip>
         </Tooltip>
       ) : (
       ) : (
-        <IconButton {...otherProps}>{props.children}</IconButton>
+        <IconButton classes={{ root: classes.iconBtn }} {...otherProps}>
+          {props.children}
+        </IconButton>
       )}
       )}
-    </>
+    </div>
   );
   );
 };
 };
 
 

+ 9 - 6
client/src/components/customDialog/CustomDialog.tsx

@@ -16,12 +16,15 @@ import CustomDialogTitle from './CustomDialogTitle';
 const useStyles = makeStyles((theme: Theme) =>
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
   createStyles({
     paper: {
     paper: {
-      minWidth: '480px',
-      borderRadius: '8px',
+      minWidth: 480,
+      borderRadius: 8,
       padding: 0,
       padding: 0,
+
+      backgroundColor: 'transparent',
     },
     },
     noticePaper: {
     noticePaper: {
-      maxWidth: '480px',
+      backgroundColor: '#fff',
+      maxWidth: 480,
     },
     },
     paperSm: {
     paperSm: {
       maxWidth: '80%',
       maxWidth: '80%',
@@ -31,12 +34,12 @@ const useStyles = makeStyles((theme: Theme) =>
     },
     },
     title: {
     title: {
       '& p': {
       '& p': {
-        fontWeight: '500',
+        fontWeight: 500,
         overflow: 'hidden',
         overflow: 'hidden',
         textOverflow: 'ellipsis',
         textOverflow: 'ellipsis',
         whiteSpace: 'nowrap',
         whiteSpace: 'nowrap',
-        maxWidth: '300px',
-        fontSize: '20px',
+        maxWidth: 300,
+        fontSize: 20,
       },
       },
     },
     },
     padding: {
     padding: {

+ 1 - 4
client/src/components/customDialog/CustomDialogTitle.tsx

@@ -14,15 +14,12 @@ const getStyles = makeStyles((theme: Theme) => ({
     justifyContent: 'space-between',
     justifyContent: 'space-between',
     alignItems: 'center',
     alignItems: 'center',
   },
   },
-  // closeButton: {
-  //   padding: theme.spacing(1),
-  // },
   title: {
   title: {
     fontWeight: 500,
     fontWeight: 500,
   },
   },
   icon: {
   icon: {
     fontSize: '24px',
     fontSize: '24px',
-    color: '#010e29',
+    color: theme.palette.milvusDark.main,
 
 
     cursor: 'pointer',
     cursor: 'pointer',
   },
   },

+ 1 - 0
client/src/components/customDialog/DeleteDialogTemplate.tsx

@@ -16,6 +16,7 @@ import { rootContext } from '../../context/Root';
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   root: {
   root: {
     maxWidth: '480px',
     maxWidth: '480px',
+    backgroundColor: '#fff',
   },
   },
   mb: {
   mb: {
     marginBottom: theme.spacing(2.5),
     marginBottom: theme.spacing(2.5),

+ 78 - 28
client/src/components/customDialog/DialogTemplate.tsx

@@ -1,4 +1,4 @@
-import { FC } from 'react';
+import { FC, useEffect, useRef, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import {
 import {
   DialogContent,
   DialogContent,
@@ -9,8 +9,29 @@ import {
 import { DialogContainerProps } from './Types';
 import { DialogContainerProps } from './Types';
 import CustomDialogTitle from './CustomDialogTitle';
 import CustomDialogTitle from './CustomDialogTitle';
 import CustomButton from '../customButton/CustomButton';
 import CustomButton from '../customButton/CustomButton';
+import CodeView from '../code/CodeView';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+  },
+  block: {
+    borderRadius: 8,
+    backgroundColor: '#fff',
+  },
+  dialog: {
+    minWidth: 480,
+  },
+  codeWrapper: {
+    width: (props: { showCode: boolean }) => (props.showCode ? 480 : 0),
+    transition: 'width 0.2s',
+  },
+  code: {
+    height: '100%',
+    // set code view padding 0 if not show
+    padding: (props: { showCode: boolean }) =>
+      props.showCode ? theme.spacing(4) : 0,
+  },
   actions: {
   actions: {
     paddingTop: theme.spacing(2),
     paddingTop: theme.spacing(2),
     justifyContent: 'space-between',
     justifyContent: 'space-between',
@@ -30,41 +51,70 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   showCancel = true,
   showCancel = true,
   showCloseIcon = true,
   showCloseIcon = true,
   leftActions,
   leftActions,
+  // needed for code mode
+  showCode = false,
+  codeBlocksData = [],
 }) => {
 }) => {
   const { t } = useTranslation('btn');
   const { t } = useTranslation('btn');
   const cancel = cancelLabel || t('cancel');
   const cancel = cancelLabel || t('cancel');
   const confirm = confirmLabel || t('confirm');
   const confirm = confirmLabel || t('confirm');
-  const classes = useStyles();
+  const classes = useStyles({ showCode });
   const onCancel = handleCancel || handleClose;
   const onCancel = handleCancel || handleClose;
 
 
+  const dialogRef = useRef(null);
+  const [dialogHeight, setDialogHeight] = useState<number>(0);
+
+  /**
+   * code mode height should not over original dialog height
+   * everytime children change, should recalculate dialog height
+   */
+  useEffect(() => {
+    if (dialogRef.current) {
+      const height = (dialogRef.current as any).offsetHeight;
+      setDialogHeight(height);
+    }
+  }, [children]);
+
   return (
   return (
-    <>
-      <CustomDialogTitle onClose={handleClose} showCloseIcon={showCloseIcon}>
-        {title}
-      </CustomDialogTitle>
-      <DialogContent>{children}</DialogContent>
-      {showActions && (
-        <DialogActions className={classes.actions}>
-          <div>{leftActions}</div>
-          <div>
-            {showCancel && (
-              <CustomButton onClick={onCancel} color="default" name="cancel">
-                {cancel}
+    <section className={classes.wrapper}>
+      <div ref={dialogRef} className={`${classes.dialog} ${classes.block}`}>
+        <CustomDialogTitle onClose={handleClose} showCloseIcon={showCloseIcon}>
+          {title}
+        </CustomDialogTitle>
+        <DialogContent>{children}</DialogContent>
+        {showActions && (
+          <DialogActions className={classes.actions}>
+            <div>{leftActions}</div>
+            <div>
+              {showCancel && (
+                <CustomButton onClick={onCancel} color="default" name="cancel">
+                  {cancel}
+                </CustomButton>
+              )}
+              <CustomButton
+                variant="contained"
+                onClick={handleConfirm}
+                color="primary"
+                disabled={confirmDisabled}
+                name="confirm"
+              >
+                {confirm}
               </CustomButton>
               </CustomButton>
-            )}
-            <CustomButton
-              variant="contained"
-              onClick={handleConfirm}
-              color="primary"
-              disabled={confirmDisabled}
-              name="confirm"
-            >
-              {confirm}
-            </CustomButton>
-          </div>
-        </DialogActions>
-      )}
-    </>
+            </div>
+          </DialogActions>
+        )}
+      </div>
+
+      <div className={`${classes.block} ${classes.codeWrapper}`}>
+        {showCode && (
+          <CodeView
+            height={dialogHeight}
+            wrapperClass={classes.code}
+            data={codeBlocksData}
+          />
+        )}
+      </div>
+    </section>
   );
   );
 };
 };
 
 

+ 4 - 0
client/src/components/customDialog/Types.ts

@@ -1,5 +1,6 @@
 import { ReactElement } from 'react';
 import { ReactElement } from 'react';
 import { DialogType } from '../../context/Types';
 import { DialogType } from '../../context/Types';
+import { CodeViewData } from '../code/Types';
 export type CustomDialogType = DialogType & {
 export type CustomDialogType = DialogType & {
   onClose: () => void;
   onClose: () => void;
   containerClass?: string;
   containerClass?: string;
@@ -32,4 +33,7 @@ export type DialogContainerProps = {
   showActions?: boolean;
   showActions?: boolean;
   showCancel?: boolean;
   showCancel?: boolean;
   leftActions?: ReactElement;
   leftActions?: ReactElement;
+  // code mode requirement
+  showCode?: boolean;
+  codeBlocksData?: CodeViewData[];
 };
 };

+ 1 - 1
client/src/components/customProgress/CustomLinearProgress.tsx

@@ -17,7 +17,7 @@ const getProgressStyles = makeStyles((theme: Theme) => ({
   percent: {
   percent: {
     minWidth: '35px',
     minWidth: '35px',
     marginLeft: theme.spacing(1),
     marginLeft: theme.spacing(1),
-    color: '#010e29',
+    color: theme.palette.milvusDark.main,
   },
   },
 }));
 }));
 
 

+ 33 - 0
client/src/components/customSwitch/CustomSwitch.tsx

@@ -0,0 +1,33 @@
+import { FormControlLabel, makeStyles, Switch, Theme } from '@material-ui/core';
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import { CustomSwitchProps } from './Types';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  label: {
+    color: '#757575',
+  },
+
+  placement: {
+    marginLeft: 0,
+  },
+}));
+
+const CustomSwitch: FC<CustomSwitchProps> = ({ onChange }) => {
+  const classes = getStyles();
+  const { t: commonTrans } = useTranslation();
+
+  return (
+    <FormControlLabel
+      classes={{
+        label: classes.label,
+        labelPlacementStart: classes.placement,
+      }}
+      label={commonTrans('view')}
+      labelPlacement="start"
+      control={<Switch color="primary" onChange={onChange} />}
+    />
+  );
+};
+
+export default CustomSwitch;

+ 3 - 0
client/src/components/customSwitch/Types.ts

@@ -0,0 +1,3 @@
+export interface CustomSwitchProps {
+  onChange: (event: React.ChangeEvent<{ checked: boolean }>) => void;
+}

+ 4 - 2
client/src/components/customTabList/CustomTabList.tsx

@@ -53,7 +53,7 @@ const a11yProps = (index: number) => {
 };
 };
 
 
 const CustomTabList: FC<ITabListProps> = props => {
 const CustomTabList: FC<ITabListProps> = props => {
-  const { tabs, activeIndex = 0, handleTabChange } = props;
+  const { tabs, activeIndex = 0, handleTabChange, wrapperClass = '' } = props;
   const classes = useStyles();
   const classes = useStyles();
   const [value, setValue] = useState<number>(activeIndex);
   const [value, setValue] = useState<number>(activeIndex);
 
 
@@ -67,10 +67,12 @@ const CustomTabList: FC<ITabListProps> = props => {
     <>
     <>
       <Tabs
       <Tabs
         classes={{
         classes={{
-          root: classes.wrapper,
+          root: `${classes.wrapper} ${wrapperClass}`,
           indicator: classes.tab,
           indicator: classes.tab,
           flexContainer: classes.tabContainer,
           flexContainer: classes.tabContainer,
         }}
         }}
+        // if not provide this property, Material will add single span element by default
+        TabIndicatorProps={{ children: <div className="tab-indicator" /> }}
         value={value}
         value={value}
         onChange={handleChange}
         onChange={handleChange}
         aria-label="tabs"
         aria-label="tabs"

+ 1 - 0
client/src/components/customTabList/Types.ts

@@ -9,6 +9,7 @@ export interface ITabListProps {
   tabs: ITab[];
   tabs: ITab[];
   activeIndex?: number;
   activeIndex?: number;
   handleTabChange?: (index: number) => void;
   handleTabChange?: (index: number) => void;
+  wrapperClass?: string;
 }
 }
 
 
 export interface ITabPanel {
 export interface ITabPanel {

+ 5 - 2
client/src/components/grid/Grid.tsx

@@ -73,8 +73,11 @@ const userStyle = makeStyles(theme => ({
 
 
 const MilvusGrid: FC<MilvusGridType> = props => {
 const MilvusGrid: FC<MilvusGridType> = props => {
   const classes = userStyle();
   const classes = userStyle();
-  const { t } = useTranslation();
-  const gridTrans = t('grid') as any;
+
+  // i18n
+  const { t: commonTrans } = useTranslation();
+  const gridTrans = commonTrans('grid');
+
   const {
   const {
     rowCount = 10,
     rowCount = 10,
     rowsPerPage = 5,
     rowsPerPage = 5,

+ 13 - 2
client/src/components/grid/Table.tsx

@@ -12,9 +12,10 @@ import { Box, Button, Typography } from '@material-ui/core';
 import EnhancedTableHead from './TableHead';
 import EnhancedTableHead from './TableHead';
 import EditableTableHead from './TableEditableHead';
 import EditableTableHead from './TableEditableHead';
 import { stableSort, getComparator } from './Utils';
 import { stableSort, getComparator } from './Utils';
-import Copy from '../../components/copy/Copy';
 import ActionBar from './ActionBar';
 import ActionBar from './ActionBar';
 import LoadingTable from './LoadingTable';
 import LoadingTable from './LoadingTable';
+import CopyButton from '../advancedSearch/CopyButton';
+import { useTranslation } from 'react-i18next';
 
 
 const useStyles = makeStyles(theme => ({
 const useStyles = makeStyles(theme => ({
   root: {
   root: {
@@ -99,6 +100,9 @@ const useStyles = makeStyles(theme => ({
     letterSpacing: '0.5px',
     letterSpacing: '0.5px',
     color: 'rgba(0, 0, 0, 0.6)',
     color: 'rgba(0, 0, 0, 0.6)',
   },
   },
+  copyBtn: {
+    marginLeft: theme.spacing(0.5),
+  },
 }));
 }));
 
 
 const EnhancedTable: FC<TableType> = props => {
 const EnhancedTable: FC<TableType> = props => {
@@ -134,6 +138,9 @@ const EnhancedTable: FC<TableType> = props => {
 
 
   const containerRef = useRef(null);
   const containerRef = useRef(null);
 
 
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy');
+
   const handleRequestSort = (event: any, property: string) => {
   const handleRequestSort = (event: any, property: string) => {
     const isAsc = orderBy === property && order === 'asc';
     const isAsc = orderBy === property && order === 'asc';
     setOrder(isAsc ? 'desc' : 'asc');
     setOrder(isAsc ? 'desc' : 'asc');
@@ -313,7 +320,11 @@ const EnhancedTable: FC<TableType> = props => {
                               )}
                               )}
 
 
                               {needCopy && row[colDef.id] && (
                               {needCopy && row[colDef.id] && (
-                                <Copy data={row[colDef.id]} />
+                                <CopyButton
+                                  label={copyTrans.label}
+                                  value={row[colDef.id]}
+                                  className={classes.copyBtn}
+                                />
                               )}
                               )}
                             </TableCell>
                             </TableCell>
                           );
                           );

+ 4 - 2
client/src/components/grid/TablePaginationActions.tsx

@@ -40,8 +40,10 @@ const useStyles = makeStyles((theme: Theme) =>
 const TablePaginationActions = (props: TablePaginationActionsProps) => {
 const TablePaginationActions = (props: TablePaginationActionsProps) => {
   const classes = useStyles();
   const classes = useStyles();
   const { count, page, rowsPerPage, onChangePage } = props;
   const { count, page, rowsPerPage, onChangePage } = props;
-  const { t } = useTranslation();
-  const gridTrans = t('grid') as any;
+
+  // i18n
+  const { t: commonTrans } = useTranslation();
+  const gridTrans = commonTrans('grid');
 
 
   const handleBackButtonClick = (
   const handleBackButtonClick = (
     event: React.MouseEvent<HTMLButtonElement>
     event: React.MouseEvent<HTMLButtonElement>

+ 2 - 1
client/src/components/grid/ToolBar.tsx

@@ -70,7 +70,8 @@ const CustomToolBar: FC<ToolBarType> = props => {
             const Icon = c.icon ? Icons[c.icon!]() : '';
             const Icon = c.icon ? Icons[c.icon!]() : '';
             const disabled = c.disabled ? c.disabled(selected) : false;
             const disabled = c.disabled ? c.disabled(selected) : false;
             // when disabled "disabledTooltip" will replace "tooltip"
             // when disabled "disabledTooltip" will replace "tooltip"
-            const tooltip = disabled && c.disabledTooltip ? c.disabledTooltip : c.tooltip;
+            const tooltip =
+              disabled && c.disabledTooltip ? c.disabledTooltip : c.tooltip;
             const isIcon = c.type === 'iconBtn';
             const isIcon = c.type === 'iconBtn';
 
 
             const btn = (
             const btn = (

+ 2 - 2
client/src/components/insert/Import.tsx

@@ -24,7 +24,7 @@ const getStyles = makeStyles((theme: Theme) => ({
       '& .selectLabel': {
       '& .selectLabel': {
         fontSize: '14px',
         fontSize: '14px',
         lineHeight: '20px',
         lineHeight: '20px',
-        color: '#010e29',
+        color: theme.palette.milvusDark.main,
       },
       },
 
 
       '& .divider': {
       '& .divider': {
@@ -187,7 +187,7 @@ const InsertImport: FC<InsertImportProps> = ({
           {insertTrans('noteTitle')}
           {insertTrans('noteTitle')}
         </Typography>
         </Typography>
         <ul className="noteList">
         <ul className="noteList">
-          {insertTrans('notes', { returnObjects: true }).map(note => (
+          {insertTrans('notes').map(note => (
             <li key={note} className="text noteItem">
             <li key={note} className="text noteItem">
               <Typography>{note}</Typography>
               <Typography>{note}</Typography>
             </li>
             </li>

+ 0 - 105
client/src/components/layout/GlobalToolbar.tsx

@@ -1,105 +0,0 @@
-import { useContext } from 'react';
-import {
-  makeStyles,
-  Theme,
-  createStyles,
-  // Divider
-} from '@material-ui/core';
-import icons from '../icons/Icons';
-// import CustomButton from '../customButton/CustomButton';
-import SimpleMenu from '../menu/SimpleMenu';
-import { useTranslation } from 'react-i18next';
-import { GlobalCreateType } from './Types';
-import { StatusEnum } from '../status/Types';
-// import { rootContext } from '../../context/Root';
-// import GlobalSearch from './GlobalSearch';
-
-const useStyles = makeStyles((theme: Theme) =>
-  createStyles({
-    root: {
-      display: 'flex',
-      background: theme.palette.common.white,
-      marginBottom: theme.spacing(3),
-      marginTop: theme.spacing(3),
-      backgroundColor: 'transparent',
-    },
-    buttonWrapper: {
-      boxSizing: 'border-box',
-      padding: theme.spacing(1, 2.5),
-      backgroundColor: theme.palette.common.white,
-      width: (props: any) => props.width,
-
-      display: 'flex',
-      flexDirection: 'column',
-    },
-    button: {
-      paddingLeft: theme.spacing(1),
-      color: theme.palette.common.black,
-      marginTop: theme.spacing(1),
-    },
-    btn: {
-      width: '100%',
-      padding: theme.spacing(1, 0),
-      fontSize: '16px',
-      lineHeight: '24px',
-    },
-    breadcrumb: {
-      padding: theme.spacing(1, 2),
-      backgroundColor: theme.palette.common.white,
-      marginLeft: '2px',
-      flex: 1,
-    },
-    divider: {
-      margin: theme.spacing(1, 0),
-    },
-    container: {},
-  })
-);
-
-const GlobalToolbar = (props: { width: String }) => {
-  const { t } = useTranslation();
-  const classes = useStyles(props);
-  const { t: btnTrans } = useTranslation('btn');
-  // const navTrans: any = t('nav');
-
-  // const SearchIcon = icons.search;
-  const AddIcon = icons.add;
-
-  // const handleGlobalSearch = () => {
-  //   openDialog({
-  //     open: true,
-  //     type: 'custom',
-  //     params: {
-  //       component: <GlobalSearch options={top100Films} />,
-  //       containerClass: classes.container,
-  //     },
-  //   });
-  // };
-
-  return (
-    <div className={classes.root}>
-      <div className={classes.buttonWrapper}>
-        <SimpleMenu
-          label={btnTrans('create')}
-          menuItems={[]}
-          buttonProps={{
-            startIcon: <AddIcon />,
-            variant: 'contained',
-            className: classes.btn,
-          }}
-        ></SimpleMenu>
-
-        {/* <CustomButton
-          startIcon={<SearchIcon />}
-          variant="outlined"
-          className={classes.button}
-          onClick={handleGlobalSearch}
-        >
-          {btnTrans('search')}
-        </CustomButton> */}
-      </div>
-    </div>
-  );
-};
-
-export default GlobalToolbar;

+ 2 - 2
client/src/components/layout/Header.tsx

@@ -61,8 +61,8 @@ const Header: FC<HeaderType> = props => {
   const { navInfo } = useContext(navContext);
   const { navInfo } = useContext(navContext);
   const { address, setAddress } = useContext(authContext);
   const { address, setAddress } = useContext(authContext);
   const history = useHistory();
   const history = useHistory();
-  const { t } = useTranslation();
-  const statusTrans: { [key in string]: string } = t('status');
+  const { t: commonTrans } = useTranslation();
+  const statusTrans = commonTrans('status');
   const BackIcon = icons.back;
   const BackIcon = icons.back;
   const LogoutIcon = icons.logout;
   const LogoutIcon = icons.logout;
 
 

+ 24 - 30
client/src/components/menu/NavMenu.tsx

@@ -139,7 +139,6 @@ const useStyles = makeStyles((theme: Theme) =>
     collapseIcon: {
     collapseIcon: {
       left: '73px',
       left: '73px',
     },
     },
-
   })
   })
 );
 );
 
 
@@ -149,8 +148,8 @@ const NavMenu: FC<NavMenuType> = props => {
   const [expanded, setExpanded] = useState<boolean>(false);
   const [expanded, setExpanded] = useState<boolean>(false);
   const [active, setActive] = useState<string>(defaultActive);
   const [active, setActive] = useState<string>(defaultActive);
 
 
-  const { t } = useTranslation();
-  const milvusTrans: { [key in string]: string } = t('milvus');
+  const { t: commonTrans } = useTranslation();
+  const milvusTrans = commonTrans('milvus');
 
 
   useEffect(() => {
   useEffect(() => {
     if (defaultActive) {
     if (defaultActive) {
@@ -176,12 +175,10 @@ const NavMenu: FC<NavMenuType> = props => {
               button
               button
               key={v.label}
               key={v.label}
               title={v.label}
               title={v.label}
-              className={
-                clsx(classes.item, {
-                  [className]: className,
-                  [classes.active]: isActive,
-                })
-              }
+              className={clsx(classes.item, {
+                [className]: className,
+                [classes.active]: isActive,
+              })}
               onClick={() => {
               onClick={() => {
                 setActive(v.label);
                 setActive(v.label);
                 v.onClick && v.onClick();
                 v.onClick && v.onClick();
@@ -192,13 +189,11 @@ const NavMenu: FC<NavMenuType> = props => {
               </ListItemIcon>
               </ListItemIcon>
 
 
               <Fade in={expanded} timeout={timeout}>
               <Fade in={expanded} timeout={timeout}>
-                <ListItemText className={classes.itemText}
-                  primary={v.label} />
+                <ListItemText className={classes.itemText} primary={v.label} />
               </Fade>
               </Fade>
             </ListItem>
             </ListItem>
           );
           );
-        })
-        }
+        })}
       </>
       </>
     );
     );
   };
   };
@@ -206,22 +201,21 @@ const NavMenu: FC<NavMenuType> = props => {
   const Logo = icons.milvus;
   const Logo = icons.milvus;
 
 
   return (
   return (
-    <List component="nav" className={
-      clsx(classes.root, {
+    <List
+      component="nav"
+      className={clsx(classes.root, {
         [classes.rootExpand]: expanded,
         [classes.rootExpand]: expanded,
         [classes.rootCollapse]: !expanded,
         [classes.rootCollapse]: !expanded,
-      })
-    }>
+      })}
+    >
       <div>
       <div>
         <div className={classes.logoWrapper}>
         <div className={classes.logoWrapper}>
           <Logo
           <Logo
             classes={{ root: classes.logo }}
             classes={{ root: classes.logo }}
-            className={
-              clsx({
-                [classes.logoExpand]: expanded,
-                [classes.logoCollapse]: !expanded,
-              })
-            }
+            className={clsx({
+              [classes.logoExpand]: expanded,
+              [classes.logoCollapse]: !expanded,
+            })}
           />
           />
           <Fade in={expanded} timeout={timeout}>
           <Fade in={expanded} timeout={timeout}>
             <Typography variant="h3" className="title">
             <Typography variant="h3" className="title">
@@ -230,13 +224,13 @@ const NavMenu: FC<NavMenuType> = props => {
           </Fade>
           </Fade>
         </div>
         </div>
         <Button
         <Button
-          onClick={() => { setExpanded(!expanded) }}
-          className={
-            clsx(classes.actionIcon, {
-              [classes.expandIcon]: expanded,
-              [classes.collapseIcon]: !expanded,
-            })
-          }
+          onClick={() => {
+            setExpanded(!expanded);
+          }}
+          className={clsx(classes.actionIcon, {
+            [classes.expandIcon]: expanded,
+            [classes.collapseIcon]: !expanded,
+          })}
         >
         >
           <ChevronRightIcon />
           <ChevronRightIcon />
         </Button>
         </Button>

+ 2 - 2
client/src/components/status/Status.tsx

@@ -41,8 +41,8 @@ const useStyles = makeStyles((theme: Theme) =>
 
 
 const Status: FC<StatusType> = props => {
 const Status: FC<StatusType> = props => {
   const { status } = props;
   const { status } = props;
-  const { t } = useTranslation();
-  const statusTrans: { [key in string]: string } = t('status');
+  const { t: commonTrans } = useTranslation();
+  const statusTrans = commonTrans('status');
   const { label, color } = useMemo(() => {
   const { label, color } = useMemo(() => {
     switch (status) {
     switch (status) {
       case StatusEnum.unloaded:
       case StatusEnum.unloaded:

+ 1 - 3
client/src/context/Auth.tsx

@@ -39,9 +39,7 @@ export const AuthProvider = (props: { children: React.ReactNode }) => {
   }, [setAddress]);
   }, [setAddress]);
 
 
   useEffect(() => {
   useEffect(() => {
-    document.title = address
-      ? `${address} - Milvus Insight`
-      : 'Milvus Insight';
+    document.title = address ? `${address} - Milvus Insight` : 'Milvus Insight';
   }, [address]);
   }, [address]);
 
 
   return (
   return (

+ 6 - 1
client/src/i18n/cn/common.ts

@@ -19,10 +19,15 @@ const commonTrans = {
   },
   },
   copy: {
   copy: {
     copy: 'Copy',
     copy: 'Copy',
-    copied: 'Copied',
+    copied: 'Copied!',
+    label: 'copy button',
   },
   },
   param: 'Parameter',
   param: 'Parameter',
   search: 'Search by name',
   search: 'Search by name',
+  code: 'Code View',
+  view: 'View Code',
+  js: 'NODE.JS',
+  py: 'PYTHON',
 };
 };
 
 
 export default commonTrans;
 export default commonTrans;

+ 6 - 1
client/src/i18n/en/common.ts

@@ -19,10 +19,15 @@ const commonTrans = {
   },
   },
   copy: {
   copy: {
     copy: 'Copy',
     copy: 'Copy',
-    copied: 'Copied',
+    copied: 'Copied!',
+    label: 'copy button',
   },
   },
   param: 'Parameter',
   param: 'Parameter',
   search: 'Search by name',
   search: 'Search by name',
+  code: 'Code View',
+  view: 'View Code',
+  js: 'NODE.JS',
+  py: 'PYTHON',
 };
 };
 
 
 export default commonTrans;
 export default commonTrans;

+ 6 - 1
client/src/index.css

@@ -10,6 +10,11 @@ body {
 }
 }
 
 
 code {
 code {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+  font-family: 'Source Code Pro', Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}
+
+code span {
+  font-family: 'Source Code Pro', Menlo, Monaco, Consolas, 'Courier New',
     monospace;
     monospace;
 }
 }

+ 1 - 1
client/src/pages/connect/Connect.tsx

@@ -55,7 +55,7 @@ const Connect = () => {
   const classes = useStyles();
   const classes = useStyles();
   const { t: commonTrans } = useTranslation();
   const { t: commonTrans } = useTranslation();
   const { t: warningTrans } = useTranslation('warning');
   const { t: warningTrans } = useTranslation('warning');
-  const milvusTrans: { [key in string]: string } = commonTrans('milvus');
+  const milvusTrans = commonTrans('milvus');
   const { t: btnTrans } = useTranslation('btn');
   const { t: btnTrans } = useTranslation('btn');
   const { t: successTrans } = useTranslation('success');
   const { t: successTrans } = useTranslation('success');
 
 

+ 5 - 1
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -26,7 +26,7 @@ const useStyles = makeStyles((theme: Theme) => ({
 
 
     margin: theme.spacing(2, 0),
     margin: theme.spacing(2, 0),
 
 
-    color: '#010e29',
+    color: theme.palette.milvusDark.main,
     fontSize: '20px',
     fontSize: '20px',
     lineHeight: '24px',
     lineHeight: '24px',
     fontWeight: 'bold',
     fontWeight: 'bold',
@@ -64,6 +64,10 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   },
   btn: {
   btn: {
     marginRight: theme.spacing(1),
     marginRight: theme.spacing(1),
+    padding: theme.spacing(0.5, 1),
+
+    lineHeight: '20px',
+    fontSize: 14,
   },
   },
 }));
 }));
 
 

+ 1 - 1
client/src/pages/overview/statisticsCard/StatisticsCard.tsx

@@ -17,7 +17,7 @@ const useStyles = makeStyles((theme: Theme) => ({
   label: {
   label: {
     fontSize: '12px',
     fontSize: '12px',
     lineHeight: '16px',
     lineHeight: '16px',
-    color: '#010e29',
+    color: theme.palette.milvusDark.main,
   },
   },
   value: {
   value: {
     fontSize: '24px',
     fontSize: '24px',

+ 60 - 20
client/src/pages/schema/Create.tsx

@@ -1,6 +1,8 @@
 import { useEffect, useMemo, useState } from 'react';
 import { useEffect, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { CodeLanguageEnum, CodeViewData } from '../../components/code/Types';
 import DialogTemplate from '../../components/customDialog/DialogTemplate';
 import DialogTemplate from '../../components/customDialog/DialogTemplate';
+import CustomSwitch from '../../components/customSwitch/CustomSwitch';
 import {
 import {
   INDEX_CONFIG,
   INDEX_CONFIG,
   INDEX_OPTIONS_MAP,
   INDEX_OPTIONS_MAP,
@@ -8,6 +10,8 @@ import {
   METRIC_TYPES_VALUES,
   METRIC_TYPES_VALUES,
 } from '../../consts/Milvus';
 } from '../../consts/Milvus';
 import { useFormValidation } from '../../hooks/Form';
 import { useFormValidation } from '../../hooks/Form';
+import { getCreateIndexJSCode } from '../../utils/code/Js';
+import { getCreateIndexPYCode } from '../../utils/code/Py';
 import { formatForm, getMetricOptions } from '../../utils/Form';
 import { formatForm, getMetricOptions } from '../../utils/Form';
 import { getEmbeddingType } from '../../utils/search';
 import { getEmbeddingType } from '../../utils/search';
 import { DataType } from '../collections/Types';
 import { DataType } from '../collections/Types';
@@ -19,12 +23,17 @@ const CreateIndex = (props: {
   fieldType: DataType;
   fieldType: DataType;
   handleCreate: (params: ParamPair[]) => void;
   handleCreate: (params: ParamPair[]) => void;
   handleCancel: () => void;
   handleCancel: () => void;
+
+  // used for code mode
+  fieldName: string;
 }) => {
 }) => {
-  const { collectionName, fieldType, handleCreate, handleCancel } = props;
+  const { collectionName, fieldType, handleCreate, handleCancel, fieldName } =
+    props;
 
 
   const { t: indexTrans } = useTranslation('index');
   const { t: indexTrans } = useTranslation('index');
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: dialogTrans } = useTranslation('dialog');
   const { t: btnTrans } = useTranslation('btn');
   const { t: btnTrans } = useTranslation('btn');
+  const { t: commonTrans } = useTranslation();
 
 
   const defaultIndexType =
   const defaultIndexType =
     fieldType === 'BinaryVector'
     fieldType === 'BinaryVector'
@@ -53,6 +62,9 @@ const CreateIndex = (props: {
     knng: '',
     knng: '',
   });
   });
 
 
+  // control whether show code mode
+  const [showCode, setShowCode] = useState<boolean>(false);
+
   const indexCreateParams = useMemo(() => {
   const indexCreateParams = useMemo(() => {
     if (!INDEX_CONFIG[indexSetting.index_type]) {
     if (!INDEX_CONFIG[indexSetting.index_type]) {
       return [];
       return [];
@@ -65,12 +77,30 @@ const CreateIndex = (props: {
     [indexSetting.index_type, fieldType]
     [indexSetting.index_type, fieldType]
   );
   );
 
 
-  const indexParams = useMemo(() => {
+  const extraParams = useMemo(() => {
     const params: { [x: string]: string } = {};
     const params: { [x: string]: string } = {};
     indexCreateParams.forEach(v => {
     indexCreateParams.forEach(v => {
       params[v] = indexSetting[v];
       params[v] = indexSetting[v];
     });
     });
-    return params;
+
+    const { index_type, metric_type } = indexSetting;
+
+    const extraParams: ParamPair[] = [
+      {
+        key: 'index_type',
+        value: index_type,
+      },
+      {
+        key: 'metric_type',
+        value: metric_type,
+      },
+      {
+        key: 'params',
+        value: JSON.stringify(params),
+      },
+    ];
+
+    return extraParams;
   }, [indexCreateParams, indexSetting]);
   }, [indexCreateParams, indexSetting]);
 
 
   const indexOptions = useMemo(() => {
   const indexOptions = useMemo(() => {
@@ -87,6 +117,25 @@ const CreateIndex = (props: {
     return form;
     return form;
   }, [indexSetting, indexCreateParams]);
   }, [indexSetting, indexCreateParams]);
 
 
+  /**
+   * create index code mode
+   */
+  const codeBlockData: CodeViewData[] = useMemo(
+    () => [
+      {
+        label: commonTrans('py'),
+        language: CodeLanguageEnum.python,
+        code: getCreateIndexPYCode({ collectionName, fieldName, extraParams }),
+      },
+      {
+        label: commonTrans('js'),
+        language: CodeLanguageEnum.javascript,
+        code: getCreateIndexJSCode({ collectionName, fieldName, extraParams }),
+      },
+    ],
+    [commonTrans, extraParams, collectionName, fieldName]
+  );
+
   const { validation, checkIsValid, disabled, setDisabled, resetValidation } =
   const { validation, checkIsValid, disabled, setDisabled, resetValidation } =
     useFormValidation(checkedForm);
     useFormValidation(checkedForm);
 
 
@@ -128,24 +177,12 @@ const CreateIndex = (props: {
   };
   };
 
 
   const handleCreateIndex = () => {
   const handleCreateIndex = () => {
-    const { index_type, metric_type } = indexSetting;
-
-    const params: ParamPair[] = [
-      {
-        key: 'index_type',
-        value: index_type,
-      },
-      {
-        key: 'metric_type',
-        value: metric_type,
-      },
-      {
-        key: 'params',
-        value: JSON.stringify(indexParams),
-      },
-    ];
+    handleCreate(extraParams);
+  };
 
 
-    handleCreate(params);
+  const handleShowCode = (event: React.ChangeEvent<{ checked: boolean }>) => {
+    const isChecked = event.target.checked;
+    setShowCode(isChecked);
   };
   };
 
 
   return (
   return (
@@ -158,6 +195,9 @@ const CreateIndex = (props: {
       confirmLabel={btnTrans('create')}
       confirmLabel={btnTrans('create')}
       handleConfirm={handleCreateIndex}
       handleConfirm={handleCreateIndex}
       confirmDisabled={disabled}
       confirmDisabled={disabled}
+      leftActions={<CustomSwitch onChange={handleShowCode} />}
+      showCode={showCode}
+      codeBlocksData={codeBlockData}
     >
     >
       <CreateForm
       <CreateForm
         updateForm={updateStepTwoForm}
         updateForm={updateStepTwoForm}

+ 23 - 4
client/src/pages/schema/IndexTypeElement.tsx

@@ -1,4 +1,11 @@
-import { FC, useCallback, useContext, useEffect, useState } from 'react';
+import {
+  FC,
+  useCallback,
+  useContext,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react';
 import Chip from '@material-ui/core/Chip';
 import Chip from '@material-ui/core/Chip';
 import { IndexHttp } from '../../http/Index';
 import { IndexHttp } from '../../http/Index';
 import { IndexState } from '../../types/Milvus';
 import { IndexState } from '../../types/Milvus';
@@ -91,6 +98,11 @@ const IndexTypeElement: FC<{
   const AddIcon = icons.add;
   const AddIcon = icons.add;
   const DeleteIcon = icons.delete;
   const DeleteIcon = icons.delete;
 
 
+  const isIndexCreating: boolean = useMemo(
+    () => status === IndexState.InProgress || status === IndexState.Unissued,
+    [status]
+  );
+
   const fetchStatus = useCallback(async () => {
   const fetchStatus = useCallback(async () => {
     // prevent delete index trigger fetching index status
     // prevent delete index trigger fetching index status
     if (data._indexType !== '' && status !== IndexState.Delete) {
     if (data._indexType !== '' && status !== IndexState.Delete) {
@@ -106,7 +118,7 @@ const IndexTypeElement: FC<{
     if (timer) {
     if (timer) {
       clearTimeout(timer);
       clearTimeout(timer);
     }
     }
-    if (data._indexType !== '' && status === IndexState.InProgress) {
+    if (data._indexType !== '' && isIndexCreating) {
       timer = setTimeout(async () => {
       timer = setTimeout(async () => {
         const res = await IndexHttp.getIndexBuildProgress(
         const res = await IndexHttp.getIndexBuildProgress(
           collectionName,
           collectionName,
@@ -129,7 +141,7 @@ const IndexTypeElement: FC<{
         }
         }
       }, 500);
       }, 500);
     }
     }
-  }, [collectionName, data._fieldName, status, data._indexType]);
+  }, [collectionName, data._fieldName, isIndexCreating, data._indexType]);
 
 
   useEffect(() => {
   useEffect(() => {
     /**
     /**
@@ -161,6 +173,7 @@ const IndexTypeElement: FC<{
         component: (
         component: (
           <CreateIndex
           <CreateIndex
             collectionName={collectionName}
             collectionName={collectionName}
+            fieldName={data._fieldName}
             fieldType={data._fieldType}
             fieldType={data._fieldType}
             handleCancel={handleCloseDialog}
             handleCancel={handleCloseDialog}
             handleCreate={requestCreateIndex}
             handleCreate={requestCreateIndex}
@@ -202,6 +215,7 @@ const IndexTypeElement: FC<{
   };
   };
 
 
   const generateElement = () => {
   const generateElement = () => {
+    // only vector type field is able to create index
     if (
     if (
       data._fieldType !== 'BinaryVector' &&
       data._fieldType !== 'BinaryVector' &&
       data._fieldType !== 'FloatVector'
       data._fieldType !== 'FloatVector'
@@ -209,6 +223,7 @@ const IndexTypeElement: FC<{
       return <div className={classes.item}>--</div>;
       return <div className={classes.item}>--</div>;
     }
     }
 
 
+    // _indexType example: FLAT
     switch (data._indexType) {
     switch (data._indexType) {
       case '': {
       case '': {
         return (
         return (
@@ -232,7 +247,11 @@ const IndexTypeElement: FC<{
         if (status === IndexState.Default || status === IndexState.Delete) {
         if (status === IndexState.Default || status === IndexState.Delete) {
           return <StatusIcon type={ChildrenStatusType.CREATING} />;
           return <StatusIcon type={ChildrenStatusType.CREATING} />;
         }
         }
-        return status === IndexState.InProgress ? (
+        /**
+         * if creating not finished, show progress bar
+         * if creating finished, show chip that contains index type
+         */
+        return isIndexCreating ? (
           <CustomLinearProgress
           <CustomLinearProgress
             value={createProgress}
             value={createProgress}
             tooltip={indexTrans('creating')}
             tooltip={indexTrans('creating')}

+ 1 - 1
client/src/pages/schema/Schema.tsx

@@ -45,7 +45,7 @@ const useStyles = makeStyles((theme: Theme) => ({
       },
       },
 
 
       '& .value': {
       '& .value': {
-        color: '#010e29',
+        color: theme.palette.milvusDark.main,
       },
       },
     },
     },
   },
   },

+ 14 - 0
client/src/styles/common.css

@@ -61,3 +61,17 @@ fieldset {
 .dialog-content::first-letter {
 .dialog-content::first-letter {
   text-transform: uppercase;
   text-transform: uppercase;
 }
 }
+
+/* change scrollbar style */
+::-webkit-scrollbar {
+  width: 8px;
+}
+
+::-webkit-scrollbar-track {
+  background-color: #f9f9f9;
+}
+
+::-webkit-scrollbar-thumb {
+  border-radius: 8px;
+  background-color: #eee;
+}

+ 5 - 0
client/src/styles/theme.ts

@@ -7,10 +7,12 @@ declare module '@material-ui/core/styles/createPalette' {
   interface Palette {
   interface Palette {
     milvusBlue: Palette['primary'];
     milvusBlue: Palette['primary'];
     milvusGrey: Palette['primary'];
     milvusGrey: Palette['primary'];
+    milvusDark: Palette['primary'];
   }
   }
   interface PaletteOptions {
   interface PaletteOptions {
     milvusBlue: PaletteOptions['primary'];
     milvusBlue: PaletteOptions['primary'];
     milvusGrey: PaletteOptions['primary'];
     milvusGrey: PaletteOptions['primary'];
+    milvusDark: PaletteOptions['primary'];
   }
   }
 }
 }
 
 
@@ -55,6 +57,9 @@ const commonThemes = {
       dark: '#82838e',
       dark: '#82838e',
       contrastText: '#f8f8fc',
       contrastText: '#f8f8fc',
     },
     },
+    milvusDark: {
+      main: '#010e29',
+    },
   },
   },
   breakpoints: {
   breakpoints: {
     values: {
     values: {

+ 14 - 0
client/src/utils/Format.ts

@@ -91,6 +91,20 @@ export const getKeyValuePairFromObj = (
   return pairs;
   return pairs;
 };
 };
 
 
+/**
+ * @param pairs e.g. [{key: 'key', value: 'value'}]
+ * @returns object, e.g. {key: value}
+ */
+export const getObjFromKeyValuePair = (
+  pairs: { key: string; value: any }[]
+): { [key in string]: any } => {
+  const obj = pairs.reduce((acc, cur) => {
+    acc[cur.key] = cur.value;
+    return acc;
+  }, {} as { [key in string]: any });
+  return obj;
+};
+
 export const getKeyValueListFromJsonString = (
 export const getKeyValueListFromJsonString = (
   json: string
   json: string
 ): { key: string; value: string }[] => {
 ): { key: string; value: string }[] => {

+ 16 - 0
client/src/utils/code/Js.ts

@@ -0,0 +1,16 @@
+import { CreateIndexCodeParam } from './Types';
+
+export const getCreateIndexJSCode = (params: CreateIndexCodeParam) => {
+  const { collectionName, fieldName, extraParams } = params;
+
+  const jsCode = `import { MilvusClient } from '@zilliz/milvus2-sdk-node';
+const client = new MilvusClient(milvus_address)
+
+client.indexManager.createIndex({
+  collection_name: ${collectionName},
+  field_name: ${fieldName},
+  extra_params: ${JSON.stringify(extraParams, null, 2)},
+});`;
+
+  return jsCode;
+};

+ 27 - 0
client/src/utils/code/Py.ts

@@ -0,0 +1,27 @@
+import { getObjFromKeyValuePair } from '../Format';
+import { parseValue } from '../Insert';
+import { CreateIndexCodeParam } from './Types';
+
+// use replacer to parse extra param from JSON to original object
+const replacer = (key: string, value: any) => {
+  if (typeof value === 'string') {
+    return parseValue(value);
+  }
+  return value;
+};
+
+export const getCreateIndexPYCode = (params: CreateIndexCodeParam) => {
+  const { collectionName, fieldName, extraParams } = params;
+  const obj = getObjFromKeyValuePair(extraParams);
+  const index = {
+    ...obj,
+    params: parseValue(obj.params),
+  };
+  const pyCode = `from pymilvus_orm import Collection,
+
+collection = Collection('${collectionName}')
+index = ${JSON.stringify(index, replacer, 4)}
+collection.create_index(${fieldName}, index)`;
+
+  return pyCode;
+};

+ 10 - 0
client/src/utils/code/Types.ts

@@ -0,0 +1,10 @@
+export interface KeyValuePairs {
+  key: string;
+  value: string;
+}
+
+export interface CreateIndexCodeParam {
+  collectionName: string;
+  fieldName: string;
+  extraParams: KeyValuePairs[];
+}

+ 157 - 0
client/yarn.lock

@@ -1869,6 +1869,13 @@
   dependencies:
   dependencies:
     "@types/node" "*"
     "@types/node" "*"
 
 
+"@types/hast@^2.0.0":
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.2.tgz#236201acca9e2695e42f713d7dd4f151dc2982e4"
+  integrity sha512-Op5W7jYgZI7AWKY5wQ0/QNMzQM7dGQPyW1rXKNiymVCy5iTfdPuGu4HhYNOM2sIv8gUfIuIdcYlXmAepwaowow==
+  dependencies:
+    "@types/unist" "*"
+
 "@types/history@*":
 "@types/history@*":
   version "4.7.8"
   version "4.7.8"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
@@ -2001,6 +2008,13 @@
     "@types/history" "*"
     "@types/history" "*"
     "@types/react" "*"
     "@types/react" "*"
 
 
+"@types/react-syntax-highlighter@^13.5.2":
+  version "13.5.2"
+  resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-13.5.2.tgz#357cc03581dc434c57c3b31f70e0eecdbf7b3ab0"
+  integrity sha512-sRZoKZBGKaE7CzMvTTgz+0x/aVR58ZYUTfB7HN76vC+yQnvo1FWtzNARBt0fGqcLGEVakEzMu/CtPzssmanu8Q==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-test-renderer@>=16.9.0":
 "@types/react-test-renderer@>=16.9.0":
   version "17.0.1"
   version "17.0.1"
   resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b"
   resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b"
@@ -2074,6 +2088,11 @@
   dependencies:
   dependencies:
     source-map "^0.6.1"
     source-map "^0.6.1"
 
 
+"@types/unist@*":
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
+  integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+
 "@types/webpack-sources@*":
 "@types/webpack-sources@*":
   version "2.1.0"
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10"
   resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10"
@@ -3398,6 +3417,21 @@ char-regex@^1.0.2:
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
   integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
   integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
 
 
+character-entities-legacy@^1.0.0:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
+  integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
+
+character-entities@^1.0.0:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
+  integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
+
+character-reference-invalid@^1.0.0:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
+  integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
+
 check-types@^11.1.1:
 check-types@^11.1.1:
   version "11.1.2"
   version "11.1.2"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
@@ -3594,6 +3628,11 @@ combined-stream@^1.0.8:
   dependencies:
   dependencies:
     delayed-stream "~1.0.0"
     delayed-stream "~1.0.0"
 
 
+comma-separated-tokens@^1.0.0:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
+  integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
+
 commander@^2.20.0:
 commander@^2.20.0:
   version "2.20.3"
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -5144,6 +5183,13 @@ fastq@^1.6.0:
   dependencies:
   dependencies:
     reusify "^1.0.4"
     reusify "^1.0.4"
 
 
+fault@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
+  integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
+  dependencies:
+    format "^0.2.0"
+
 faye-websocket@^0.11.3:
 faye-websocket@^0.11.3:
   version "0.11.4"
   version "0.11.4"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -5316,6 +5362,11 @@ form-data@^3.0.0:
     combined-stream "^1.0.8"
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
     mime-types "^2.1.12"
 
 
+format@^0.2.0:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+  integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
 forwarded@0.2.0:
 forwarded@0.2.0:
   version "0.2.0"
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -5660,6 +5711,22 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
     minimalistic-assert "^1.0.1"
 
 
+hast-util-parse-selector@^2.0.0:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
+  integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
+
+hastscript@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
+  integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    comma-separated-tokens "^1.0.0"
+    hast-util-parse-selector "^2.0.0"
+    property-information "^5.0.0"
+    space-separated-tokens "^1.0.0"
+
 he@^1.2.0:
 he@^1.2.0:
   version "1.2.0"
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -5675,6 +5742,11 @@ highlight-words-core@^1.2.0:
   resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa"
   resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa"
   integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==
   integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==
 
 
+highlight.js@^10.4.1, highlight.js@~10.7.0:
+  version "10.7.3"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
+  integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
+
 history@^4.9.0:
 history@^4.9.0:
   version "4.10.1"
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
   resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
@@ -6100,6 +6172,19 @@ is-accessor-descriptor@^1.0.0:
   dependencies:
   dependencies:
     kind-of "^6.0.0"
     kind-of "^6.0.0"
 
 
+is-alphabetical@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
+  integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
+
+is-alphanumerical@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
+  integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
+  dependencies:
+    is-alphabetical "^1.0.0"
+    is-decimal "^1.0.0"
+
 is-arguments@^1.0.4:
 is-arguments@^1.0.4:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
@@ -6198,6 +6283,11 @@ is-date-object@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
   integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
   integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
 
 
+is-decimal@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
+  integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
+
 is-descriptor@^0.1.0:
 is-descriptor@^0.1.0:
   version "0.1.6"
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -6272,6 +6362,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
   dependencies:
   dependencies:
     is-extglob "^2.1.1"
     is-extglob "^2.1.1"
 
 
+is-hexadecimal@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
+  integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
+
 is-in-browser@^1.0.2, is-in-browser@^1.1.3:
 is-in-browser@^1.0.2, is-in-browser@^1.1.3:
   version "1.1.3"
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
   resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
@@ -7339,6 +7434,14 @@ lower-case@^2.0.2:
   dependencies:
   dependencies:
     tslib "^2.0.3"
     tslib "^2.0.3"
 
 
+lowlight@^1.17.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
+  integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
+  dependencies:
+    fault "^1.0.0"
+    highlight.js "~10.7.0"
+
 lru-cache@^5.1.1:
 lru-cache@^5.1.1:
   version "5.1.1"
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -8203,6 +8306,18 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
     pbkdf2 "^3.0.3"
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
     safe-buffer "^5.1.1"
 
 
+parse-entities@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
+  integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
+  dependencies:
+    character-entities "^1.0.0"
+    character-entities-legacy "^1.0.0"
+    character-reference-invalid "^1.0.0"
+    is-alphanumerical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-hexadecimal "^1.0.0"
+
 parse-json@^4.0.0:
 parse-json@^4.0.0:
   version "4.0.0"
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -9121,6 +9236,11 @@ prepend-http@^1.0.0:
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
   integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
   integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
 
 
+prettier@2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d"
+  integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==
+
 pretty-bytes@^5.3.0:
 pretty-bytes@^5.3.0:
   version "5.6.0"
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
   resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@@ -9144,6 +9264,11 @@ pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2:
     ansi-styles "^4.0.0"
     ansi-styles "^4.0.0"
     react-is "^17.0.1"
     react-is "^17.0.1"
 
 
+prismjs@^1.22.0, prismjs@~1.24.0:
+  version "1.24.1"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.1.tgz#c4d7895c4d6500289482fa8936d9cdd192684036"
+  integrity sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==
+
 process-nextick-args@~2.0.0:
 process-nextick-args@~2.0.0:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -9196,6 +9321,13 @@ prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
     object-assign "^4.1.1"
     object-assign "^4.1.1"
     react-is "^16.8.1"
     react-is "^16.8.1"
 
 
+property-information@^5.0.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
+  integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
+  dependencies:
+    xtend "^4.0.0"
+
 proxy-addr@~2.0.5:
 proxy-addr@~2.0.5:
   version "2.0.7"
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -9543,6 +9675,17 @@ react-scripts@4.0.3:
   optionalDependencies:
   optionalDependencies:
     fsevents "^2.1.3"
     fsevents "^2.1.3"
 
 
+react-syntax-highlighter@^15.4.4:
+  version "15.4.4"
+  resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.4.4.tgz#dc9043f19e7bd063ff3ea78986d22a6eaa943b2a"
+  integrity sha512-PsOFHNTzkb3OroXdoR897eKN5EZ6grht1iM+f1lJSq7/L0YVnkJaNVwC3wEUYPOAmeyl5xyer1DjL6MrumO6Zw==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    highlight.js "^10.4.1"
+    lowlight "^1.17.0"
+    prismjs "^1.22.0"
+    refractor "^3.2.0"
+
 react-transition-group@^4.4.0:
 react-transition-group@^4.4.0:
   version "4.4.2"
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
@@ -9650,6 +9793,15 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
     strip-indent "^3.0.0"
 
 
+refractor@^3.2.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.4.0.tgz#62bd274b06c942041f390c371b676eb67cb0a678"
+  integrity sha512-dBeD02lC5eytm9Gld2Mx0cMcnR+zhSnsTfPpWqFaMgUMJfC9A6bcN3Br/NaXrnBJcuxnLFR90k1jrkaSyV8umg==
+  dependencies:
+    hastscript "^6.0.0"
+    parse-entities "^2.0.0"
+    prismjs "~1.24.0"
+
 regenerate-unicode-properties@^8.2.0:
 regenerate-unicode-properties@^8.2.0:
   version "8.2.0"
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -10422,6 +10574,11 @@ sourcemap-codec@^1.4.4:
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 
 
+space-separated-tokens@^1.0.0:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
+  integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
+
 spdx-correct@^3.0.0:
 spdx-correct@^3.0.0:
   version "3.1.1"
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"