FileUploader.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { css } from '@emotion/css';
  2. import React, { Dispatch, SetStateAction, useState } from 'react';
  3. import SVG from 'react-inlinesvg';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { FileDropzone, useStyles2, Button, DropzoneFile, Field } from '@grafana/ui';
  6. import { MediaType } from '../types';
  7. interface Props {
  8. setFormData: Dispatch<SetStateAction<FormData>>;
  9. mediaType: MediaType;
  10. setUpload: Dispatch<SetStateAction<boolean>>;
  11. newValue: string;
  12. error: ErrorResponse;
  13. }
  14. interface ErrorResponse {
  15. message: string;
  16. }
  17. export function FileDropzoneCustomChildren({ secondaryText = 'Drag and drop here or browse' }) {
  18. const styles = useStyles2(getStyles);
  19. return (
  20. <div className={styles.iconWrapper}>
  21. <small className={styles.small}>{secondaryText}</small>
  22. <Button type="button" icon="upload">
  23. Upload
  24. </Button>
  25. </div>
  26. );
  27. }
  28. export const FileUploader = ({ mediaType, setFormData, setUpload, error }: Props) => {
  29. const styles = useStyles2(getStyles);
  30. const [dropped, setDropped] = useState<boolean>(false);
  31. const [file, setFile] = useState<string>('');
  32. const Preview = () => (
  33. <Field label="Preview">
  34. <div className={styles.iconPreview}>
  35. {mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />}
  36. {mediaType === MediaType.Image && <img src={file} className={styles.img} />}
  37. </div>
  38. </Field>
  39. );
  40. const onFileRemove = (file: DropzoneFile) => {
  41. fetch(`/api/storage/delete/upload/${file.file.name}`, {
  42. method: 'DELETE',
  43. }).catch((error) => console.error('cannot delete file', error));
  44. };
  45. const acceptableFiles =
  46. mediaType === 'icon' ? 'image/svg+xml' : 'image/jpeg,image/png,image/gif,image/png, image/webp';
  47. return (
  48. <FileDropzone
  49. readAs="readAsBinaryString"
  50. onFileRemove={onFileRemove}
  51. options={{
  52. accept: acceptableFiles,
  53. multiple: false,
  54. onDrop: (acceptedFiles: File[]) => {
  55. let formData = new FormData();
  56. formData.append('file', acceptedFiles[0]);
  57. setFile(URL.createObjectURL(acceptedFiles[0]));
  58. setDropped(true);
  59. setFormData(formData);
  60. setUpload(true);
  61. },
  62. }}
  63. >
  64. {error.message !== '' && dropped ? (
  65. <p>{error.message}</p>
  66. ) : dropped ? (
  67. <Preview />
  68. ) : (
  69. <FileDropzoneCustomChildren />
  70. )}
  71. </FileDropzone>
  72. );
  73. };
  74. function getStyles(theme: GrafanaTheme2, isDragActive?: boolean) {
  75. return {
  76. container: css`
  77. display: flex;
  78. flex-direction: column;
  79. width: 100%;
  80. `,
  81. dropzone: css`
  82. display: flex;
  83. flex: 1;
  84. flex-direction: column;
  85. align-items: center;
  86. padding: ${theme.spacing(6)};
  87. border-radius: 2px;
  88. border: 2px dashed ${theme.colors.border.medium};
  89. background-color: ${isDragActive ? theme.colors.background.secondary : theme.colors.background.primary};
  90. cursor: pointer;
  91. `,
  92. iconWrapper: css`
  93. display: flex;
  94. flex-direction: column;
  95. align-items: center;
  96. `,
  97. acceptMargin: css`
  98. margin: ${theme.spacing(2, 0, 1)};
  99. `,
  100. small: css`
  101. color: ${theme.colors.text.secondary};
  102. margin-bottom: ${theme.spacing(2)};
  103. `,
  104. iconContainer: css`
  105. display: flex;
  106. flex-direction: column;
  107. width: 80%;
  108. align-items: center;
  109. align-self: center;
  110. `,
  111. iconPreview: css`
  112. width: 238px;
  113. height: 198px;
  114. border: 1px solid ${theme.colors.border.medium};
  115. display: flex;
  116. align-items: center;
  117. justify-content: center;
  118. `,
  119. img: css`
  120. width: 147px;
  121. height: 147px;
  122. fill: ${theme.colors.text.primary};
  123. `,
  124. };
  125. }