github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/pages/adhoc/components/FileUploader.tsx (about)

     1  /* eslint-disable react/jsx-props-no-spreading, jsx-a11y/role-supports-aria-props */
     2  import React, { useCallback, useState } from 'react';
     3  import { useDropzone } from 'react-dropzone';
     4  import Button from '@webapp/ui/Button';
     5  import type { DropzoneOptions } from 'react-dropzone';
     6  import { faFileUpload } from '@fortawesome/free-solid-svg-icons/faFileUpload';
     7  import classNames from 'classnames/bind';
     8  import Dropdown, { MenuItem } from '@webapp/ui/Dropdown';
     9  import {
    10    SpyNameFirstClass,
    11    SpyNameFirstClassType,
    12  } from '@pyroscope/models/src/spyName';
    13  import { units as possibleUnits, UnitsType } from '@pyroscope/models/src';
    14  import { humanizeSpyname, isJSONFile, humanizeUnits } from './humanize';
    15  import UploadIcon from './UploadIcon';
    16  import styles from './FileUploader.module.scss';
    17  
    18  const cx = classNames.bind(styles);
    19  
    20  type UploadArgsType = {
    21    file: File;
    22    spyName?: string;
    23    units?: string;
    24  };
    25  interface Props {
    26    setFile: ({ file, spyName, units }: UploadArgsType) => void;
    27    className?: string;
    28  }
    29  
    30  export default function FileUploader({ setFile: onUpload, className }: Props) {
    31    const [file, setFile] = useState<File>();
    32    const [spyName, setSpyName] = useState<SpyNameFirstClassType>('gospy');
    33    const [units, setUnits] = useState<UnitsType>('samples');
    34    type onDrop = Required<DropzoneOptions>['onDrop'];
    35    const onDrop = useCallback<onDrop>(
    36      (acceptedFiles) => {
    37        if (acceptedFiles.length > 1) {
    38          throw new Error('Only a single file at a time is accepted.');
    39        }
    40  
    41        acceptedFiles.forEach((f) => {
    42          setFile(f);
    43        });
    44      },
    45      [setFile]
    46    );
    47  
    48    const showLanguageAndUnits = (file && !isJSONFile(file)) || false;
    49  
    50    const { getRootProps, getInputProps } = useDropzone({
    51      multiple: false,
    52      onDrop,
    53    });
    54  
    55    const descriptionOrFilename = file
    56      ? file.name
    57      : 'Upload profile in pprof, json, or collapsed format';
    58  
    59    const onUploadClick = () => {
    60      if (file) {
    61        onUpload({
    62          file,
    63          spyName: showLanguageAndUnits ? spyName : undefined,
    64          units: showLanguageAndUnits ? units : undefined,
    65        });
    66      }
    67    };
    68  
    69    return (
    70      <>
    71        <section className={`${styles.container} ${className}`}>
    72          <div {...getRootProps()} className={styles.dragAndDropContainer}>
    73            <input {...getInputProps()} />
    74            <p className={styles.headerMain}>{descriptionOrFilename}</p>
    75            <div className={styles.iconContainer}>
    76              <UploadIcon />
    77            </div>
    78            <p className={styles.uploadBtnPreLabel}>
    79              Drag & Drop
    80              <span className={styles.uploadBtnPreLabel}>or</span>
    81            </p>
    82            <div className={styles.uploadBtnWrapper}>
    83              <Button
    84                kind="primary"
    85                className={cx({
    86                  [styles.uploadButton]: true,
    87                  [styles.disabled]: !!file,
    88                })}
    89                icon={faFileUpload}
    90                disabled={!!file}
    91              >
    92                Select a file
    93              </Button>
    94            </div>
    95          </div>
    96        </section>
    97        {showLanguageAndUnits && (
    98          <div className={styles.dropdowns}>
    99            <Dropdown
   100              value={`Profile language: ${humanizeSpyname(spyName)}`}
   101              onItemClick={(e) => setSpyName(e.value)}
   102              label="Profile language"
   103            >
   104              {SpyNameFirstClass.map((name, index) => (
   105                <MenuItem
   106                  className={cx({
   107                    [styles.activeDropdownItem]: spyName === name,
   108                  })}
   109                  key={String(index) + name}
   110                  value={name}
   111                >
   112                  {humanizeSpyname(name)}
   113                </MenuItem>
   114              ))}
   115            </Dropdown>
   116            <Dropdown
   117              value={`Profile units: ${humanizeUnits(units)}`}
   118              onItemClick={(e) => setUnits(e.value)}
   119              label="Profile units"
   120            >
   121              {possibleUnits.map((name, index) => (
   122                <MenuItem
   123                  className={cx({
   124                    [styles.activeDropdownItem]: units === name,
   125                  })}
   126                  key={String(index) + name}
   127                  value={name}
   128                >
   129                  {humanizeUnits(name)}
   130                </MenuItem>
   131              ))}
   132            </Dropdown>
   133          </div>
   134        )}
   135        <div className={styles.uploadWrapper}>
   136          <Button kind="primary" disabled={!file} onClick={onUploadClick}>
   137            Save
   138          </Button>
   139        </div>
   140      </>
   141    );
   142  }