ar-design
Version:
AR Design is a (react | nextjs) ui library.
181 lines (180 loc) • 8.85 kB
JavaScript
"use client";
import React, { useCallback, useEffect, useRef, useState } from "react";
import "../../../assets/css/components/form/upload/styles.css";
import { ARIcon } from "../../icons";
import Dropzone from "./Dropzone";
import Button from "../button";
import List from "./List";
import Utils from "../../../libs/infrastructure/shared/Utils";
const Upload = ({ text, files, onChange, allowedTypes, maxSize, type = "list", direction = "column", size, fullWidth, multiple, }) => {
// refs
const _input = useRef(null);
const _arUplaod = useRef(null);
// refs -> File Data
const _validationErrors = useRef([]);
// variables
const [className, setClassName] = useState(["button"]);
// states
const [selectedFiles, setSelectedFiles] = useState([]);
const [validationErrors, setValidationErrors] = useState([]);
// methods
const handleFileChange = useCallback((files) => {
const _files = Array.from(files ?? []);
setSelectedFiles((prev) => {
const previousFileNames = prev.map((f) => f.name);
const newFiles = _files.filter((f) => !previousFileNames.includes(f.name)) ?? [];
return [...prev, ...newFiles];
});
}, []);
const handleFileRemove = useCallback((fileToRemove) => {
const dataTransfer = new DataTransfer();
setSelectedFiles((prev) => {
const newList = prev.filter((x) => x.name !== fileToRemove.name);
newList.forEach((file) => dataTransfer.items.add(file));
if (_input.current)
_input.current.files = dataTransfer.files;
if (newList.length === 0)
setClassName((prev) => prev.filter((c) => c !== "has-file"));
return newList;
});
}, []);
const handleValidationFile = useCallback((file) => {
const newErrors = [];
if (allowedTypes) {
if (!allowedTypes.includes(file.type)) {
newErrors.push({ fileName: file.name, message: "Geçersiz dosya türü." });
_validationErrors.current.push(file.name);
}
}
if (maxSize) {
const _maxSize = maxSize * 1024 * 1024; // MB
if (file.size > _maxSize) {
newErrors.push({ fileName: file.name, message: "Dosya boyutu çok büyük." });
_validationErrors.current.push(file.name);
}
}
setValidationErrors((prev) => [...prev, ...newErrors]);
}, []);
const handleFileToBase64 = useCallback((file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.result && typeof reader.result === "string") {
resolve(reader.result);
}
else {
reject(new Error("Failed to read the file"));
}
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}, []);
const handleDrag = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setClassName((prev) => {
const index = prev.findIndex((c) => c === "dragging");
if (index === -1)
return [...prev, "dragging"];
return prev;
});
}
else {
setClassName((prev) => prev.filter((c) => c !== "dragging"));
}
}, []);
const handleDrop = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
const files = e.dataTransfer.files;
if (files && files.length > 0)
setSelectedFiles(Array.from(files));
setClassName((prev) => prev.filter((c) => c !== "dragging"));
}, []);
const renderUploadFile = (params) => {
return (React.createElement("div", { ref: _arUplaod, className: "ar-upload" },
React.createElement("input", { ref: _input, type: "file", onChange: (event) => handleFileChange(event.target.files), multiple: multiple }),
params.children));
};
// useEffects
useEffect(() => {
(async () => {
const dataTransfer = new DataTransfer();
const fileFormData = new FormData();
setValidationErrors([]);
_validationErrors.current = [];
if (_input.current) {
if (selectedFiles.length === 0) {
if (_input.current)
_input.current.files = dataTransfer.files;
onChange(fileFormData, [], [], false);
return;
}
// Seçilmiş olan dosyalar validasyona gönderiliyor.
selectedFiles.forEach((f) => handleValidationFile(f));
const inValidFiles = Array.from(new Set(_validationErrors.current));
// Input içerisine dosyalar aktarılıyor.
selectedFiles.forEach((f) => dataTransfer.items.add(f));
_input.current.files = dataTransfer.files;
// Geçerli olan dosyalar alındı...
const validFiles = [...selectedFiles.filter((x) => !inValidFiles.includes(x.name))];
validFiles.forEach((f) => fileFormData.append("file", f));
// Geçerli olan dosyalar base64'e dönüştürülüyor...
const base64Array = await Promise.all(validFiles.map((validFile) => handleFileToBase64(validFile)));
onChange(fileFormData, validFiles, base64Array, _validationErrors.current.length === 0);
// Eğer dosya varsa.
setClassName((prev) => {
const index = prev.findIndex((c) => c === "has-file");
if (index === -1)
return [...prev, "has-file"];
return prev;
});
}
})();
}, [selectedFiles]);
useEffect(() => {
if (Utils.DeepEqual(files, selectedFiles))
return;
setSelectedFiles(files);
}, [files]);
useEffect(() => {
if (type === "dropzone")
setClassName((prev) => [...prev, "dropzone"]);
}, []);
switch (type) {
case "list":
case "grid":
return renderUploadFile({
children: (React.createElement(React.Fragment, null,
React.createElement(Button, { type: "button", variant: "outlined", color: "gray", icon: { element: React.createElement(ARIcon, { icon: "CloudUpload-Fill" }) }, onClick: () => {
if (_input.current)
_input.current.click();
}, fullWidth: fullWidth, size: size }, text && React.createElement("span", null, text)),
React.createElement(List, { type: type, direction: direction, selectedFiles: selectedFiles ?? [], validationErrors: validationErrors, handleFileToBase64: handleFileToBase64, handleFileRemove: handleFileRemove }))),
});
case "dropzone":
return renderUploadFile({
children: (React.createElement("div", { className: "ar-upload-button" },
React.createElement("div", { className: className.map((c) => c).join(" "), onDragEnter: handleDrag, onDragLeave: handleDrag, onDragOver: handleDrag, onDrop: handleDrop, onClick: () => {
if (_input.current)
_input.current.click();
} },
React.createElement(Dropzone, { selectedFiles: selectedFiles ?? [], validationErrors: validationErrors, handleFileToBase64: handleFileToBase64, handleFileRemove: handleFileRemove }),
selectedFiles && selectedFiles.length === 0 && (React.createElement(React.Fragment, null,
React.createElement("div", { className: "upload" },
React.createElement(ARIcon, { icon: "CloudUpload-Fill", size: 32 }),
React.createElement("div", { className: "properies" },
allowedTypes && (React.createElement("div", { className: "allow-types" }, allowedTypes?.map((allowedType) => allowedType.split("/")[1].toLocaleUpperCase()).join(", "))),
maxSize && React.createElement("div", { className: "max-size" },
"up to ",
maxSize,
"MB"))),
text && React.createElement("span", null, text)))))),
});
default:
return null;
}
};
export default Upload;