@grafana/ui
Version:
Grafana Components Library
275 lines (270 loc) • 9.86 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var css = require('@emotion/css');
var React = require('react');
var reactDropzone = require('react-dropzone');
var data = require('@grafana/data');
var i18n = require('@grafana/i18n');
var ThemeContext = require('../../themes/ThemeContext.cjs');
var uniqueId = require('../../utils/uniqueId.cjs');
var Alert = require('../Alert/Alert.cjs');
var FieldContext = require('../Forms/FieldContext.cjs');
var Icon = require('../Icon/Icon.cjs');
var FileListItem = require('./FileListItem.cjs');
;
function FileDropzone({
options,
children,
readAs,
onLoad,
fileListRenderer,
onFileRemove,
id: idProp
}) {
const [files, setFiles] = React.useState([]);
const [fileErrors, setErrorMessages] = React.useState([]);
const fieldContext = FieldContext.useFieldContext();
const id = idProp != null ? idProp : fieldContext.id;
const formattedSize = data.getValueFormat("decbytes")((options == null ? void 0 : options.maxSize) ? options == null ? void 0 : options.maxSize : 0);
const setFileProperty = React.useCallback(
(customFile, action) => {
setFiles((oldFiles) => {
return oldFiles.map((oldFile) => {
if (oldFile.id === customFile.id) {
action(oldFile);
return oldFile;
}
return oldFile;
});
});
},
[]
);
const onDrop = React.useCallback(
(acceptedFiles, rejectedFiles, event) => {
let customFiles = acceptedFiles.map(mapToCustomFile);
if ((options == null ? void 0 : options.multiple) === false) {
setFiles(customFiles);
} else {
setFiles((oldFiles) => [...oldFiles, ...customFiles]);
}
setErrors(rejectedFiles);
if (options == null ? void 0 : options.onDrop) {
options.onDrop(acceptedFiles, rejectedFiles, event);
} else {
for (const customFile of customFiles) {
const reader = new FileReader();
const read = () => {
if (readAs) {
reader[readAs](customFile.file);
} else {
reader.readAsText(customFile.file);
}
};
setFileProperty(customFile, (fileToModify) => {
fileToModify.abortUpload = () => {
reader.abort();
};
fileToModify.retryUpload = () => {
setFileProperty(customFile, (fileToModify2) => {
fileToModify2.error = null;
fileToModify2.progress = void 0;
});
read();
};
});
reader.onabort = () => {
setFileProperty(customFile, (fileToModify) => {
fileToModify.error = new DOMException("Aborted");
});
};
reader.onprogress = (event2) => {
setFileProperty(customFile, (fileToModify) => {
fileToModify.progress = event2.loaded;
});
};
reader.onload = () => {
onLoad == null ? void 0 : onLoad(reader.result);
};
reader.onerror = () => {
setFileProperty(customFile, (fileToModify) => {
fileToModify.error = reader.error;
});
};
read();
}
}
},
[onLoad, options, readAs, setFileProperty]
);
const removeFile = (file) => {
const newFiles = files.filter((f) => file.id !== f.id);
setFiles(newFiles);
onFileRemove == null ? void 0 : onFileRemove(file);
};
const { getRootProps, getInputProps, isDragActive } = reactDropzone.useDropzone({
...options,
useFsAccessApi: false,
onDrop,
accept: transformAcceptToNewFormat(options == null ? void 0 : options.accept)
});
const theme = ThemeContext.useTheme2();
const styles = getStyles(theme, isDragActive);
const fileList = files.map((file) => {
if (fileListRenderer) {
return fileListRenderer(file, removeFile);
}
return /* @__PURE__ */ jsxRuntime.jsx(FileListItem.FileListItem, { file, removeFile }, file.id);
});
const setErrors = (rejectedFiles) => {
let errors = [];
rejectedFiles.map((rejectedFile) => {
rejectedFile.errors.map((newError) => {
if (errors.findIndex((presentError) => {
return presentError.code === newError.code && presentError.message === newError.message;
}) === -1) {
errors.push(newError);
}
});
});
setErrorMessages(errors);
};
const renderErrorMessages = (errors) => {
const size = data.formattedValueToString(formattedSize);
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.errorAlert, children: /* @__PURE__ */ jsxRuntime.jsx(
Alert.Alert,
{
title: i18n.t("grafana-ui.file-dropzone.error-title", "Upload failed"),
severity: "error",
onRemove: clearAlert,
children: errors.map((error) => {
switch (error.code) {
case reactDropzone.ErrorCode.FileTooLarge:
return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(i18n.Trans, { i18nKey: "grafana-ui.file-dropzone.file-too-large", children: [
"File is larger than ",
{ size }
] }) }, error.message + error.code);
default:
return /* @__PURE__ */ jsxRuntime.jsx("div", { children: error.message }, error.message + error.code);
}
})
}
) });
};
const clearAlert = () => {
setErrorMessages([]);
};
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.container, children: [
/* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "dropzone", ...getRootProps({ className: styles.dropzone }), children: [
/* @__PURE__ */ jsxRuntime.jsx("input", { ...getInputProps(), id }),
children != null ? children : /* @__PURE__ */ jsxRuntime.jsx(FileDropzoneDefaultChildren, { primaryText: getPrimaryText(files, options) })
] }),
fileErrors.length > 0 && renderErrorMessages(fileErrors),
/* @__PURE__ */ jsxRuntime.jsxs("small", { className: css.cx(styles.small, styles.acceptContainer), children: [
(options == null ? void 0 : options.maxSize) && `Max file size: ${data.formattedValueToString(formattedSize)}`,
(options == null ? void 0 : options.maxSize) && (options == null ? void 0 : options.accept) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.acceptSeparator, children: "|" }),
(options == null ? void 0 : options.accept) && getAcceptedFileTypeText(options.accept)
] }),
fileList
] });
}
function getMimeTypeByExtension(ext) {
if (["txt", "json", "csv", "xls", "yml"].some((e) => ext.match(e))) {
return "text/plain";
}
return "application/octet-stream";
}
function transformAcceptToNewFormat(accept) {
if (typeof accept === "string") {
return {
[getMimeTypeByExtension(accept)]: [accept]
};
}
if (Array.isArray(accept)) {
return accept.reduce((prev, current) => {
const mime = getMimeTypeByExtension(current);
prev[mime] = prev[mime] ? [...prev[mime], current] : [current];
return prev;
}, {});
}
return accept;
}
function FileDropzoneDefaultChildren({ primaryText = "Drop file here or click to upload", secondaryText = "" }) {
const theme = ThemeContext.useTheme2();
const styles = getStyles(theme);
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: css.cx(styles.defaultDropZone), "data-testid": "file-drop-zone-default-children", children: [
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { className: css.cx(styles.icon), name: "upload", size: "xl" }),
/* @__PURE__ */ jsxRuntime.jsx("h6", { className: css.cx(styles.primaryText), children: primaryText }),
/* @__PURE__ */ jsxRuntime.jsx("small", { className: styles.small, children: secondaryText })
] });
}
function getPrimaryText(files, options) {
if ((options == null ? void 0 : options.multiple) === void 0 || (options == null ? void 0 : options.multiple)) {
return "Upload file";
}
return files.length ? "Replace file" : "Upload file";
}
function getAcceptedFileTypeText(accept) {
if (typeof accept === "string") {
return `Accepted file type: ${accept}`;
}
if (Array.isArray(accept)) {
return `Accepted file types: ${accept.join(", ")}`;
}
return `Accepted file types: ${Object.values(accept).flat().join(", ")}`;
}
function mapToCustomFile(file) {
return {
id: uniqueId.uniqueId("file"),
file,
error: null
};
}
function getStyles(theme, isDragActive) {
return {
container: css.css({
display: "flex",
flexDirection: "column",
width: "100%",
padding: theme.spacing(2),
borderRadius: theme.shape.radius.default,
border: `1px dashed ${theme.colors.border.strong}`,
backgroundColor: isDragActive ? theme.colors.background.secondary : theme.colors.background.primary,
cursor: "pointer",
alignItems: "center",
justifyContent: "center"
}),
dropzone: css.css({
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column"
}),
defaultDropZone: css.css({
textAlign: "center"
}),
icon: css.css({
marginBottom: theme.spacing(1)
}),
primaryText: css.css({
marginBottom: theme.spacing(1)
}),
acceptContainer: css.css({
textAlign: "center",
margin: 0
}),
acceptSeparator: css.css({
margin: `0 ${theme.spacing(1)}`
}),
small: css.css({
color: theme.colors.text.secondary
}),
errorAlert: css.css({
paddingTop: "10px"
})
};
}
exports.FileDropzone = FileDropzone;
exports.FileDropzoneDefaultChildren = FileDropzoneDefaultChildren;
//# sourceMappingURL=FileDropzone.cjs.map