"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DropZone = void 0; const react_1 = __importStar(require("react")); const styled_components_1 = __importDefault(require("styled-components")); const label_1 = require("../../atoms/label"); const box_1 = require("../../atoms/box"); const text_1 = require("../../atoms/text"); const message_box_1 = require("../message-box"); const drop_zone_item_1 = require("./drop-zone-item"); const human_file_size_1 = require("../../utils/human-file-size"); const validateContentType = (mimeTypes, mimeType) => { if (!mimeTypes || !mimeTypes.length) { return true; } return mimeTypes.includes(mimeType); }; const validateSize = (maxSize, size) => { if (!maxSize) { return true; } if (!size) { return true; } return +maxSize >= +size; }; const inUnit = (size, unit) => { if (!size) { return ''; } return human_file_size_1.humanFileSize(size, unit); }; const UploadInput = styled_components_1.default.input ` font-size: 100px; position: absolute; left: 0; top: 0; opacity: 0; bottom: 0; cursor: pointer; width: 100%; `; const StyledDropZone = styled_components_1.default(box_1.Box) ` border: 1px dashed ${({ theme }) => theme.colors.grey80}; position: relative; text-align: center; & ${label_1.Label} { color: ${({ theme }) => theme.colors.grey60}; font-size: ${({ theme }) => theme.fontSizes.xs}; padding-right: 4px; letter-spacing: 1px; } `; /** * @classdesc * * * * DropZone which can be used for uploading files. * * ### usage * * ```javascript * import { DropZone, DropZoneProps } from '@adminjs/design-system' * ``` * * how to use it in your custom component.tsx (TypesScript): * ``` * import React, { useState } from 'react' * import { DropZone, Label, BasePropertyProps } from '@adminjs/design-system' * import { unflatten } from 'flat' * * const UploadPhoto: React.FC = (props) => { * const { property, record, onChange } = props * * const onUpload = (files: FileList) => { * const newRecord = {...record} * const file = files.length && files[0] * * onChange({ * ...newRecord, * params: { * ...newRecord.params, * [property.name]: file, * } * }) * event.preventDefault() * } * * return ( * * * * * ) * } * ``` * @hideconstructor * @component * @subcategory Molecules * @see FileSizeUnit * @see OnDropDownChange * @see DropZoneProps * @see {@link https://storybook.adminjs.co/?path=/story/designsystem-molecules-dropzone--default Storybook} * @example Single file with validation * const maxSize = 1024 * 100 * const mimeTypes = ['application/pdf'] * const onUpload = (files) => { alert(files,length ? files[0].name : 'no files' ) } * return ( * * * * ) * * @example Multi file of photos * const mimeTypes = ['image/png'] * const onUpload = (files) => { alert(files.length ? files.length : 'no files' ) } * return ( * * * * ) * @section design-system */ const DropZone = (props) => { const { validate, onChange, multiple, files: filesFromProps, uploadLimitIn } = props, other = __rest(props, ["validate", "onChange", "multiple", "files", "uploadLimitIn"]); const [, setIsDragging] = react_1.useState(false); const [error, setError] = react_1.useState(null); const [filesToUpload, setFilesToUpload] = react_1.useState(filesFromProps !== null && filesFromProps !== void 0 ? filesFromProps : []); react_1.useEffect(() => { if (filesFromProps) { setFilesToUpload(filesFromProps); } }, [filesFromProps]); const onDragEnter = () => setIsDragging(true); const onDragLeave = () => setIsDragging(false); const onDragOver = () => setIsDragging(true); const removeItem = react_1.useCallback((index) => { const newItems = [...filesToUpload]; newItems.splice(index, 1); if (onChange) { onChange(newItems); } setFilesToUpload(newItems); }, [filesToUpload, setFilesToUpload, onChange]); const onDrop = react_1.useCallback((event) => { event.preventDefault(); setIsDragging(false); const { files } = (event.dataTransfer || event.target); const validatedFiles = []; for (let i = 0; i < files.length; i += 1) { const file = files.item(i); if (!file) { return; } if (validate && !validateSize(validate.maxSize, file && file.size)) { setError({ message: `File: ${file.name} size is too big`, title: 'Wrong Size' }); return; } if (validate && !validateContentType(validate.mimeTypes, file.type)) { setError({ message: `File: ${file.name} has unsupported type: ${file.type}`, title: 'Wrong Type' }); return; } validatedFiles.push(file); setError(null); } let newFiles; if (!multiple && validatedFiles.length) { newFiles = [validatedFiles[0]]; } else { newFiles = [ ...filesToUpload, ...validatedFiles, ]; } if (onChange) { onChange(newFiles); } setFilesToUpload(newFiles); }, [onChange, setFilesToUpload, setIsDragging]); const displayUploadLimit = react_1.useCallback(() => { if (validate && validate.maxSize) { return inUnit(validate.maxSize, uploadLimitIn); } return ''; }, [validate]); return (react_1.default.createElement(box_1.Box, null, react_1.default.createElement(StyledDropZone, Object.assign({ onDragEnter: onDragEnter, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop }, other, { p: "xl" }), react_1.default.createElement(UploadInput, { type: "file", onChange: (event) => onDrop(event), multiple: multiple }), react_1.default.createElement(box_1.Box, null, react_1.default.createElement(text_1.Text, { fontSize: "sm" }, "Pick or Drop File here to upload it."), react_1.default.createElement(box_1.Box, null, validate && validate.maxSize ? (react_1.default.createElement(text_1.Text, { variant: "xs", color: "grey60", lineHeight: "default", mt: "sm" }, react_1.default.createElement(label_1.Label, { inline: true, uppercase: true }, "Max size:"), displayUploadLimit())) : '', validate && validate.mimeTypes && validate.mimeTypes.length ? (react_1.default.createElement(text_1.Text, { variant: "xs", color: "grey60", lineHeight: "default", mt: "sm" }, validate.mimeTypes.join(', '))) : ''))), error ? (react_1.default.createElement(message_box_1.MessageBox, { mt: "default", variant: "danger", size: "sm", icon: "Warning", message: error.title, onCloseClick: () => setError(null) }, error.message)) : '', filesToUpload.map((file, index) => ( // eslint-disable-next-line react/no-array-index-key react_1.default.createElement(drop_zone_item_1.DropZoneItem, { file: file, key: index, onRemove: () => removeItem(index) }))))); }; exports.DropZone = DropZone; exports.default = DropZone;