mirror of https://github.com/grafana/grafana
Storage: Upload button (#52346)
parent
4aae9d1567
commit
524948515c
@ -0,0 +1,118 @@ |
|||||||
|
import { css } from '@emotion/css'; |
||||||
|
import React, { FormEvent, useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data'; |
||||||
|
import { ConfirmModal, FileUpload, useStyles2 } from '@grafana/ui'; |
||||||
|
|
||||||
|
import { filenameAlreadyExists, getGrafanaStorage } from './storage'; |
||||||
|
import { StorageView, UploadReponse } from './types'; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
setErrorMessages: (errors: string[]) => void; |
||||||
|
setPath: (p: string, view?: StorageView) => void; |
||||||
|
path: string; |
||||||
|
fileNames: string[]; |
||||||
|
} |
||||||
|
|
||||||
|
const fileFormats = 'image/jpg, image/jpeg, image/png, image/gif, image/webp'; |
||||||
|
|
||||||
|
export function UploadButton({ setErrorMessages, setPath, path, fileNames }: Props) { |
||||||
|
const styles = useStyles2(getStyles); |
||||||
|
|
||||||
|
const [file, setFile] = useState<File | undefined>(undefined); |
||||||
|
const [filenameExists, setFilenameExists] = useState(false); |
||||||
|
const [fileUploadKey, setFileUploadKey] = useState(1); |
||||||
|
const [isConfirmOpen, setIsConfirmOpen] = useState(true); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setFileUploadKey((prev) => prev + 1); |
||||||
|
}, [file]); |
||||||
|
|
||||||
|
const onUpload = (rsp: UploadReponse) => { |
||||||
|
console.log('Uploaded: ' + path); |
||||||
|
if (rsp.path) { |
||||||
|
setPath(rsp.path); |
||||||
|
} else { |
||||||
|
setPath(path); // back to data
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const doUpload = async (fileToUpload: File, overwriteExistingFile: boolean) => { |
||||||
|
if (!fileToUpload) { |
||||||
|
setErrorMessages(['Please select a file.']); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const rsp = await getGrafanaStorage().upload(path, fileToUpload, overwriteExistingFile); |
||||||
|
if (rsp.status !== 200) { |
||||||
|
setErrorMessages([rsp.message]); |
||||||
|
} else { |
||||||
|
onUpload(rsp); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const onFileUpload = (event: FormEvent<HTMLInputElement>) => { |
||||||
|
setErrorMessages([]); |
||||||
|
|
||||||
|
const fileToUpload = |
||||||
|
event.currentTarget.files && event.currentTarget.files.length > 0 && event.currentTarget.files[0] |
||||||
|
? event.currentTarget.files[0] |
||||||
|
: undefined; |
||||||
|
if (fileToUpload) { |
||||||
|
setFile(fileToUpload); |
||||||
|
|
||||||
|
const fileExists = filenameAlreadyExists(fileToUpload.name, fileNames); |
||||||
|
if (!fileExists) { |
||||||
|
setFilenameExists(false); |
||||||
|
doUpload(fileToUpload, false).then((r) => {}); |
||||||
|
} else { |
||||||
|
setFilenameExists(true); |
||||||
|
setIsConfirmOpen(true); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const onOverwriteConfirm = () => { |
||||||
|
if (file) { |
||||||
|
doUpload(file, true).then((r) => {}); |
||||||
|
setIsConfirmOpen(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const onOverwriteDismiss = () => { |
||||||
|
setFile(undefined); |
||||||
|
setFilenameExists(false); |
||||||
|
setIsConfirmOpen(false); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<FileUpload accept={fileFormats} onFileUpload={onFileUpload} key={fileUploadKey} className={styles.uploadButton}> |
||||||
|
Upload |
||||||
|
</FileUpload> |
||||||
|
|
||||||
|
{file && filenameExists && ( |
||||||
|
<ConfirmModal |
||||||
|
isOpen={isConfirmOpen} |
||||||
|
body={ |
||||||
|
<div> |
||||||
|
<p>{file?.name}</p> |
||||||
|
<p>A file with this name already exists.</p> |
||||||
|
<p>What would you like to do?</p> |
||||||
|
</div> |
||||||
|
} |
||||||
|
title={'This file already exists'} |
||||||
|
confirmText={'Replace'} |
||||||
|
onConfirm={onOverwriteConfirm} |
||||||
|
onDismiss={onOverwriteDismiss} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({ |
||||||
|
uploadButton: css` |
||||||
|
margin-right: ${theme.spacing(2)}; |
||||||
|
`,
|
||||||
|
}); |
@ -1,156 +0,0 @@ |
|||||||
import { css } from '@emotion/css'; |
|
||||||
import React, { useState } from 'react'; |
|
||||||
import SVG from 'react-inlinesvg'; |
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data'; |
|
||||||
import { Alert, Button, ButtonGroup, Checkbox, Field, FileDropzone, useStyles2 } from '@grafana/ui'; |
|
||||||
|
|
||||||
import { filenameAlreadyExists, getGrafanaStorage } from './storage'; |
|
||||||
import { UploadReponse } from './types'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
folder: string; |
|
||||||
onUpload: (rsp: UploadReponse) => void; |
|
||||||
fileNames: string[]; |
|
||||||
} |
|
||||||
|
|
||||||
interface ErrorResponse { |
|
||||||
message: string; |
|
||||||
} |
|
||||||
|
|
||||||
const FileDropzoneCustomChildren = ({ secondaryText = 'Drag and drop here or browse' }) => { |
|
||||||
const styles = useStyles2(getStyles); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className={styles.iconWrapper}> |
|
||||||
<small className={styles.small}>{secondaryText}</small> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
export const UploadView = ({ folder, onUpload, fileNames }: Props) => { |
|
||||||
const [file, setFile] = useState<File | undefined>(undefined); |
|
||||||
|
|
||||||
const styles = useStyles2(getStyles); |
|
||||||
|
|
||||||
const [error, setError] = useState<ErrorResponse>({ message: '' }); |
|
||||||
const [overwriteExistingFile, setOverwriteExistingFile] = useState(false); |
|
||||||
|
|
||||||
const Preview = () => { |
|
||||||
if (!file) { |
|
||||||
return <></>; |
|
||||||
} |
|
||||||
const isImage = file.type?.startsWith('image/'); |
|
||||||
const isSvg = file.name?.endsWith('.svg'); |
|
||||||
|
|
||||||
const src = URL.createObjectURL(file); |
|
||||||
return ( |
|
||||||
<Field label="Preview"> |
|
||||||
<div className={styles.iconPreview}> |
|
||||||
{isSvg && <SVG src={src} className={styles.img} />} |
|
||||||
{isImage && !isSvg && <img src={src} className={styles.img} />} |
|
||||||
</div> |
|
||||||
</Field> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const doUpload = async () => { |
|
||||||
if (!file) { |
|
||||||
setError({ message: 'please select a file' }); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
const rsp = await getGrafanaStorage().upload(folder, file, overwriteExistingFile); |
|
||||||
if (rsp.status !== 200) { |
|
||||||
setError(rsp); |
|
||||||
} else { |
|
||||||
onUpload(rsp); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const filenameExists = file ? filenameAlreadyExists(file.name, fileNames) : false; |
|
||||||
const isUploadDisabled = !file || (filenameExists && !overwriteExistingFile); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<FileDropzone |
|
||||||
readAs="readAsBinaryString" |
|
||||||
onFileRemove={() => { |
|
||||||
setFile(undefined); |
|
||||||
}} |
|
||||||
options={{ |
|
||||||
accept: { 'image/*': ['.jpg', '.jpeg', '.png', '.gif', '.webp'] }, |
|
||||||
multiple: false, |
|
||||||
onDrop: (acceptedFiles: File[]) => { |
|
||||||
setFile(acceptedFiles[0]); |
|
||||||
}, |
|
||||||
}} |
|
||||||
> |
|
||||||
{error.message !== '' ? <p>{error.message}</p> : Boolean(file) ? <Preview /> : <FileDropzoneCustomChildren />} |
|
||||||
</FileDropzone> |
|
||||||
|
|
||||||
{file && filenameExists && ( |
|
||||||
<div className={styles.alert}> |
|
||||||
<Alert title={`${file.name} already exists`} severity="error"> |
|
||||||
<Checkbox |
|
||||||
value={overwriteExistingFile} |
|
||||||
onChange={() => setOverwriteExistingFile(!overwriteExistingFile)} |
|
||||||
label="Overwrite existing file" |
|
||||||
/> |
|
||||||
</Alert> |
|
||||||
</div> |
|
||||||
)} |
|
||||||
|
|
||||||
<ButtonGroup> |
|
||||||
<Button className={styles.button} variant={'primary'} disabled={isUploadDisabled} onClick={doUpload}> |
|
||||||
Upload |
|
||||||
</Button> |
|
||||||
</ButtonGroup> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({ |
|
||||||
resourcePickerPopover: css` |
|
||||||
border-radius: ${theme.shape.borderRadius()}; |
|
||||||
box-shadow: ${theme.shadows.z3}; |
|
||||||
background: ${theme.colors.background.primary}; |
|
||||||
border: 1px solid ${theme.colors.border.medium}; |
|
||||||
`,
|
|
||||||
resourcePickerPopoverContent: css` |
|
||||||
width: 315px; |
|
||||||
font-size: ${theme.typography.bodySmall.fontSize}; |
|
||||||
min-height: 184px; |
|
||||||
padding: ${theme.spacing(1)}; |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
`,
|
|
||||||
button: css` |
|
||||||
margin: 12px 20px 5px; |
|
||||||
`,
|
|
||||||
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}; |
|
||||||
`,
|
|
||||||
iconWrapper: css` |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
align-items: center; |
|
||||||
`,
|
|
||||||
small: css` |
|
||||||
color: ${theme.colors.text.secondary}; |
|
||||||
margin-bottom: ${theme.spacing(2)}; |
|
||||||
`,
|
|
||||||
alert: css` |
|
||||||
padding-top: 10px; |
|
||||||
`,
|
|
||||||
}); |
|
Loading…
Reference in new issue