mirror of https://github.com/grafana/grafana
FileStorage: Add upload form (#46749)
* move upload to http * use storage from grafanads * rever gomod changes * fix test * wip * add upload func * update upload func * writing to uploads * edit response from service * use dropzone for UI * modify response struct in service * better read file * set content type for svg * restrict file types upload * add test and clean up errors * pass test * fix backend lint errors * limit type of files on FE * add TODO for after merge * rebase with storage changes * comment out unused function * update UI to not have 2 uploads * only call upload on select * use utils function to find * in path * show preview on drag over * not allowing upload of svg * add preview to upload tab * no console.log * resolve conflicts * refactor log line * fix failing BE test Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Artur Wierzbicki <artur.wierzbicki@grafana.com>pull/48373/head
parent
4b417c8f3e
commit
900d9bf9a1
@ -0,0 +1,132 @@ |
||||
import { css } from '@emotion/css'; |
||||
import React, { Dispatch, SetStateAction, useState } from 'react'; |
||||
import SVG from 'react-inlinesvg'; |
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data'; |
||||
import { FileDropzone, useStyles2, Button, DropzoneFile, Field } from '@grafana/ui'; |
||||
|
||||
import { MediaType } from '../types'; |
||||
interface Props { |
||||
setFormData: Dispatch<SetStateAction<FormData>>; |
||||
mediaType: MediaType; |
||||
setUpload: Dispatch<SetStateAction<boolean>>; |
||||
newValue: string; |
||||
error: ErrorResponse; |
||||
} |
||||
interface ErrorResponse { |
||||
message: string; |
||||
} |
||||
export function FileDropzoneCustomChildren({ secondaryText = 'Drag and drop here or browse' }) { |
||||
const styles = useStyles2(getStyles); |
||||
|
||||
return ( |
||||
<div className={styles.iconWrapper}> |
||||
<small className={styles.small}>{secondaryText}</small> |
||||
<Button type="button" icon="upload"> |
||||
Upload |
||||
</Button> |
||||
</div> |
||||
); |
||||
} |
||||
export const FileUploader = ({ mediaType, setFormData, setUpload, error }: Props) => { |
||||
const styles = useStyles2(getStyles); |
||||
const [dropped, setDropped] = useState<boolean>(false); |
||||
const [file, setFile] = useState<string>(''); |
||||
|
||||
const Preview = () => ( |
||||
<Field label="Preview"> |
||||
<div className={styles.iconPreview}> |
||||
{mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />} |
||||
{mediaType === MediaType.Image && <img src={file} className={styles.img} />} |
||||
</div> |
||||
</Field> |
||||
); |
||||
|
||||
const onFileRemove = (file: DropzoneFile) => { |
||||
fetch(`/api/storage/delete/upload/${file.file.name}`, { |
||||
method: 'DELETE', |
||||
}).catch((error) => console.error('cannot delete file', error)); |
||||
}; |
||||
|
||||
const acceptableFiles = |
||||
mediaType === 'icon' ? 'image/svg+xml' : 'image/jpeg,image/png,image/gif,image/png, image/webp'; |
||||
return ( |
||||
<FileDropzone |
||||
readAs="readAsBinaryString" |
||||
onFileRemove={onFileRemove} |
||||
options={{ |
||||
accept: acceptableFiles, |
||||
multiple: false, |
||||
onDrop: (acceptedFiles: File[]) => { |
||||
let formData = new FormData(); |
||||
formData.append('file', acceptedFiles[0]); |
||||
setFile(URL.createObjectURL(acceptedFiles[0])); |
||||
setDropped(true); |
||||
setFormData(formData); |
||||
setUpload(true); |
||||
}, |
||||
}} |
||||
> |
||||
{error.message !== '' && dropped ? ( |
||||
<p>{error.message}</p> |
||||
) : dropped ? ( |
||||
<Preview /> |
||||
) : ( |
||||
<FileDropzoneCustomChildren /> |
||||
)} |
||||
</FileDropzone> |
||||
); |
||||
}; |
||||
|
||||
function getStyles(theme: GrafanaTheme2, isDragActive?: boolean) { |
||||
return { |
||||
container: css` |
||||
display: flex; |
||||
flex-direction: column; |
||||
width: 100%; |
||||
`,
|
||||
dropzone: css` |
||||
display: flex; |
||||
flex: 1; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
padding: ${theme.spacing(6)}; |
||||
border-radius: 2px; |
||||
border: 2px dashed ${theme.colors.border.medium}; |
||||
background-color: ${isDragActive ? theme.colors.background.secondary : theme.colors.background.primary}; |
||||
cursor: pointer; |
||||
`,
|
||||
iconWrapper: css` |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
`,
|
||||
acceptMargin: css` |
||||
margin: ${theme.spacing(2, 0, 1)}; |
||||
`,
|
||||
small: css` |
||||
color: ${theme.colors.text.secondary}; |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
`,
|
||||
iconContainer: css` |
||||
display: flex; |
||||
flex-direction: column; |
||||
width: 80%; |
||||
align-items: center; |
||||
align-self: center; |
||||
`,
|
||||
iconPreview: css` |
||||
width: 238px; |
||||
height: 198px; |
||||
border: 1px solid ${theme.colors.border.medium}; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
`,
|
||||
img: css` |
||||
width: 147px; |
||||
height: 147px; |
||||
fill: ${theme.colors.text.primary}; |
||||
`,
|
||||
}; |
||||
} |
Loading…
Reference in new issue