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 }