zentrixui
Version:
ZentrixUI - A modern, highly customizable and accessible React file upload component library with multiple variants, JSON-based configuration, and excellent developer experience.
313 lines (312 loc) • 9.32 kB
JavaScript
const getFileExtension = (filename) => {
const lastDotIndex = filename.lastIndexOf(".");
if (lastDotIndex === -1) return "";
return filename.substring(lastDotIndex + 1).toLowerCase();
};
const validateFileType = (file, allowedTypes = ["*"], allowedExtensions = ["*"]) => {
const errors = [];
const warnings = [];
const fileType = file.type;
const fileName = file.name;
const fileExtension = getFileExtension(fileName);
if (!fileExtension && fileName.indexOf(".") === -1) {
warnings.push("File has no extension, type validation may be unreliable");
}
if (allowedTypes.includes("*") && allowedExtensions.includes("*")) {
return { isValid: true, errors, warnings };
}
if (!allowedTypes.includes("*")) {
let typeValid = false;
for (const allowedType of allowedTypes) {
if (allowedType === "*") {
typeValid = true;
break;
}
if (allowedType.endsWith("/*")) {
const baseType = allowedType.slice(0, -2);
if (fileType.startsWith(baseType + "/")) {
typeValid = true;
break;
}
}
if (fileType === allowedType) {
typeValid = true;
break;
}
}
if (!typeValid) {
errors.push({
code: "invalid-file-type",
message: `File type "${fileType}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`,
type: "type"
});
}
}
if (!allowedExtensions.includes("*")) {
const extensionValid = allowedExtensions.some(
(ext) => ext === "*" || ext.toLowerCase() === fileExtension
);
if (!extensionValid) {
errors.push({
code: "invalid-file-extension",
message: `File extension ".${fileExtension}" is not allowed. Allowed extensions: ${allowedExtensions.join(", ")}`,
type: "type"
});
}
}
return {
isValid: errors.length === 0,
errors,
warnings
};
};
const formatFileSize = (bytes) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
const validateFileSize = (file, maxSize, minSize = 0) => {
const errors = [];
const warnings = [];
if (file.size > maxSize) {
errors.push({
code: "file-too-large",
message: `File size ${formatFileSize(file.size)} exceeds maximum allowed size of ${formatFileSize(maxSize)}`,
type: "size"
});
}
if (file.size < minSize) {
errors.push({
code: "file-too-small",
message: `File size ${formatFileSize(file.size)} is below minimum required size of ${formatFileSize(minSize)}`,
type: "size"
});
}
if (file.size > maxSize * 0.8) {
warnings.push("File is approaching the maximum size limit");
}
if (file.size === 0) {
warnings.push("File appears to be empty");
}
return {
isValid: errors.length === 0,
errors,
warnings
};
};
const validateFileCount = (files, maxFiles, currentFileCount = 0) => {
const errors = [];
const warnings = [];
const totalFiles = files.length + currentFileCount;
if (totalFiles > maxFiles) {
errors.push({
code: "too-many-files",
message: `Cannot upload ${totalFiles} files. Maximum allowed is ${maxFiles}`,
type: "count"
});
}
if (totalFiles > maxFiles * 0.8) {
warnings.push(`Approaching file limit (${totalFiles}/${maxFiles})`);
}
return {
isValid: errors.length === 0,
errors,
warnings
};
};
const validateImageDimensions = (file, maxWidth, maxHeight, minWidth, minHeight) => {
return new Promise((resolve) => {
const errors = [];
const warnings = [];
if (!file.type.startsWith("image/")) {
resolve({ isValid: true, errors, warnings });
return;
}
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(url);
const { width, height } = img;
if (maxWidth && width > maxWidth) {
errors.push({
code: "image-width-too-large",
message: `Image width ${width}px exceeds maximum allowed width of ${maxWidth}px`,
type: "dimensions"
});
}
if (maxHeight && height > maxHeight) {
errors.push({
code: "image-height-too-large",
message: `Image height ${height}px exceeds maximum allowed height of ${maxHeight}px`,
type: "dimensions"
});
}
if (minWidth && width < minWidth) {
errors.push({
code: "image-width-too-small",
message: `Image width ${width}px is below minimum required width of ${minWidth}px`,
type: "dimensions"
});
}
if (minHeight && height < minHeight) {
errors.push({
code: "image-height-too-small",
message: `Image height ${height}px is below minimum required height of ${minHeight}px`,
type: "dimensions"
});
}
resolve({
isValid: errors.length === 0,
errors,
warnings
});
};
img.onerror = () => {
URL.revokeObjectURL(url);
errors.push({
code: "invalid-image",
message: "Unable to read image file or file is corrupted",
type: "validation"
});
resolve({ isValid: false, errors, warnings });
};
img.src = url;
});
};
const validateFile = async (file, config, currentFileCount = 0) => {
const allErrors = [];
const allWarnings = [];
const typeValidation = validateFileType(
file,
config.validation.allowedTypes,
config.validation.allowedExtensions
);
allErrors.push(...typeValidation.errors);
allWarnings.push(...typeValidation.warnings);
const sizeValidation = validateFileSize(
file,
config.validation.maxSize,
config.validation.minSize || 0
);
allErrors.push(...sizeValidation.errors);
allWarnings.push(...sizeValidation.warnings);
if (config.validation.validateDimensions && file.type.startsWith("image/")) {
const dimensionValidation = await validateImageDimensions(
file,
config.validation.maxWidth,
config.validation.maxHeight,
config.validation.minWidth,
config.validation.minHeight
);
allErrors.push(...dimensionValidation.errors);
allWarnings.push(...dimensionValidation.warnings);
}
return {
isValid: allErrors.length === 0,
errors: allErrors,
warnings: allWarnings
};
};
const validateFiles = async (files, config, currentFileCount = 0) => {
const validFiles = [];
const rejectedFiles = [];
const allWarnings = [];
let totalSize = 0;
const countValidation = validateFileCount(files, config.validation.maxFiles, currentFileCount);
if (!countValidation.isValid) {
const countError = countValidation.errors[0];
rejectedFiles.push(...files.map((file) => ({
file,
errors: [countError]
})));
return {
validFiles: [],
rejectedFiles,
totalSize: 0,
warnings: countValidation.warnings
};
}
allWarnings.push(...countValidation.warnings);
for (const file of files) {
const validation = await validateFile(file, config, currentFileCount + validFiles.length);
if (validation.isValid) {
validFiles.push(file);
totalSize += file.size;
} else {
rejectedFiles.push({
file,
errors: validation.errors
});
}
allWarnings.push(...validation.warnings);
}
return {
validFiles,
rejectedFiles,
totalSize,
warnings: allWarnings
};
};
const isImageFile = (file) => {
return file.type.startsWith("image/");
};
const isVideoFile = (file) => {
return file.type.startsWith("video/");
};
const isAudioFile = (file) => {
return file.type.startsWith("audio/");
};
const isDocumentFile = (file) => {
const documentTypes = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
"text/csv",
"application/rtf"
];
return documentTypes.includes(file.type);
};
const createValidationError = (code, message, type) => {
return { code, message, type };
};
const validateAcceptAttribute = (accept) => {
if (!accept || accept === "*") return true;
const parts = accept.split(",").map((part) => part.trim());
for (const part of parts) {
if (part.startsWith(".")) {
if (!/^\.[a-zA-Z0-9]+$/.test(part)) return false;
} else if (part.includes("/")) {
const [type, subtype] = part.split("/");
if (!type || !subtype) return false;
if (!/^[a-zA-Z0-9\-]+$/.test(type)) return false;
if (!/^[a-zA-Z0-9\-*]+$/.test(subtype)) return false;
} else {
return false;
}
}
return true;
};
export {
createValidationError,
formatFileSize,
getFileExtension,
isAudioFile,
isDocumentFile,
isImageFile,
isVideoFile,
validateAcceptAttribute,
validateFile,
validateFileCount,
validateFileSize,
validateFileType,
validateFiles,
validateImageDimensions
};
//# sourceMappingURL=file-validation.js.map