1import { css } from '@emotion/css'; 2import { formattedValueToString, getValueFormat, GrafanaTheme2 } from '@grafana/data'; 3import React from 'react'; 4import { useStyles2 } from '../../themes'; 5import { trimFileName } from '../../utils/file'; 6import { Button } from '../Button'; 7import { Icon } from '../Icon/Icon'; 8import { IconButton } from '../IconButton/IconButton'; 9import { DropzoneFile } from './FileDropzone'; 10 11export const REMOVE_FILE = 'Remove file'; 12export interface FileListItemProps { 13 file: DropzoneFile; 14 removeFile?: (file: DropzoneFile) => void; 15} 16 17export function FileListItem({ file: customFile, removeFile }: FileListItemProps) { 18 const styles = useStyles2(getStyles); 19 const { file, progress, error, abortUpload, retryUpload } = customFile; 20 21 const renderRightSide = () => { 22 if (error) { 23 return ( 24 <> 25 <span className={styles.error}>{error.message}</span> 26 {retryUpload && ( 27 <IconButton 28 type="button" 29 aria-label="Retry" 30 name="sync" 31 tooltip="Retry" 32 tooltipPlacement="top" 33 onClick={retryUpload} 34 /> 35 )} 36 {removeFile && ( 37 <IconButton 38 className={retryUpload ? styles.marginLeft : ''} 39 type="button" 40 name="trash-alt" 41 onClick={() => removeFile(customFile)} 42 tooltip={REMOVE_FILE} 43 aria-label={REMOVE_FILE} 44 /> 45 )} 46 </> 47 ); 48 } 49 50 if (progress && file.size > progress) { 51 return ( 52 <> 53 <progress className={styles.progressBar} max={file.size} value={progress} /> 54 <span className={styles.paddingLeft}>{Math.round((progress / file.size) * 100)}%</span> 55 {abortUpload && ( 56 <Button variant="secondary" type="button" fill="text" onClick={abortUpload}> 57 Cancel upload 58 </Button> 59 )} 60 </> 61 ); 62 } 63 return ( 64 removeFile && ( 65 <IconButton 66 name="trash-alt" 67 onClick={() => removeFile(customFile)} 68 tooltip={REMOVE_FILE} 69 aria-label={REMOVE_FILE} 70 type="button" 71 tooltipPlacement="top" 72 /> 73 ) 74 ); 75 }; 76 77 const valueFormat = getValueFormat('decbytes')(file.size); 78 79 return ( 80 <div className={styles.fileListContainer}> 81 <span className={styles.fileNameWrapper}> 82 <Icon name="file-blank" size="lg" aria-hidden={true} /> 83 <span className={styles.padding}>{trimFileName(file.name)}</span> 84 <span>{formattedValueToString(valueFormat)}</span> 85 </span> 86 87 <div className={styles.fileNameWrapper}>{renderRightSide()}</div> 88 </div> 89 ); 90} 91 92function getStyles(theme: GrafanaTheme2) { 93 return { 94 fileListContainer: css` 95 width: 100%; 96 display: flex; 97 flex-direction: row; 98 align-items: center; 99 justify-content: space-between; 100 padding: ${theme.spacing(2)}; 101 border: 1px dashed ${theme.colors.border.medium}; 102 background-color: ${theme.colors.background.secondary}; 103 margin-top: ${theme.spacing(1)}; 104 `, 105 fileNameWrapper: css` 106 display: flex; 107 flex-direction: row; 108 align-items: center; 109 `, 110 padding: css` 111 padding: ${theme.spacing(0, 1)}; 112 `, 113 paddingLeft: css` 114 padding-left: ${theme.spacing(2)}; 115 `, 116 marginLeft: css` 117 margin-left: ${theme.spacing(1)}; 118 `, 119 error: css` 120 padding-right: ${theme.spacing(2)}; 121 color: ${theme.colors.error.text}; 122 `, 123 progressBar: css` 124 border-radius: ${theme.spacing(1)}; 125 height: 4px; 126 ::-webkit-progress-bar { 127 background-color: ${theme.colors.border.weak}; 128 border-radius: ${theme.spacing(1)}; 129 } 130 ::-webkit-progress-value { 131 background-color: ${theme.colors.primary.main}; 132 border-radius: ${theme.spacing(1)}; 133 } 134 `, 135 }; 136} 137