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