zentrixui
Version:
ZentrixUI - A modern, highly customizable and accessible React file upload component library with multiple variants, JSON-based configuration, and excellent developer experience.
676 lines (674 loc) • 21.3 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const theme = require("./chunks/theme-Bz28eVNI.cjs");
const ERROR_MESSAGES = {
// File validation errors
"file-too-large": {
title: "File Too Large",
userMessage: "The selected file is too large to upload.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Try compressing the file before uploading",
"Choose a smaller file",
"Contact support if you need to upload larger files"
]
},
"file-too-small": {
title: "File Too Small",
userMessage: "The selected file is too small.",
severity: "low",
recoverable: true,
retryable: false,
suggestions: [
"Make sure the file contains content",
"Choose a different file"
]
},
"invalid-file-type": {
title: "Invalid File Type",
userMessage: "This file type is not supported.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Check the list of supported file types",
"Convert your file to a supported format",
"Choose a different file"
]
},
"invalid-file-extension": {
title: "Invalid File Extension",
userMessage: "This file extension is not allowed.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Rename the file with a supported extension",
"Convert your file to a supported format"
]
},
"too-many-files": {
title: "Too Many Files",
userMessage: "You have selected too many files.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Remove some files and try again",
"Upload files in smaller batches"
]
},
"image-width-too-large": {
title: "Image Too Wide",
userMessage: "The image width exceeds the maximum allowed size.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Resize the image to a smaller width",
"Use an image editing tool to reduce dimensions"
]
},
"image-height-too-large": {
title: "Image Too Tall",
userMessage: "The image height exceeds the maximum allowed size.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Resize the image to a smaller height",
"Use an image editing tool to reduce dimensions"
]
},
"invalid-image": {
title: "Invalid Image",
userMessage: "The image file appears to be corrupted or invalid.",
severity: "high",
recoverable: true,
retryable: false,
suggestions: [
"Try opening the image in an image viewer to verify it works",
"Choose a different image file",
"Re-save the image in a different format"
]
},
// Network and upload errors
"network-error": {
title: "Network Error",
userMessage: "Unable to connect to the server.",
severity: "high",
recoverable: true,
retryable: true,
suggestions: [
"Check your internet connection",
"Try again in a few moments",
"Contact support if the problem persists"
]
},
"upload-timeout": {
title: "Upload Timeout",
userMessage: "The upload took too long and was cancelled.",
severity: "medium",
recoverable: true,
retryable: true,
suggestions: [
"Try uploading a smaller file",
"Check your internet connection speed",
"Try again when your connection is more stable"
]
},
"server-error": {
title: "Server Error",
userMessage: "The server encountered an error while processing your upload.",
severity: "high",
recoverable: true,
retryable: true,
suggestions: [
"Try again in a few minutes",
"Contact support if the error continues"
]
},
"quota-exceeded": {
title: "Storage Quota Exceeded",
userMessage: "You have reached your storage limit.",
severity: "high",
recoverable: false,
retryable: false,
suggestions: [
"Delete some existing files to free up space",
"Upgrade your storage plan",
"Contact support for assistance"
]
},
"permission-denied": {
title: "Permission Denied",
userMessage: "You do not have permission to upload files.",
severity: "high",
recoverable: false,
retryable: false,
suggestions: [
"Contact your administrator for access",
"Make sure you are logged in with the correct account"
]
},
// File system errors
"file-read-error": {
title: "Cannot Read File",
userMessage: "Unable to read the selected file.",
severity: "high",
recoverable: true,
retryable: false,
suggestions: [
"Make sure the file is not corrupted",
"Try selecting the file again",
"Choose a different file"
]
},
"file-access-denied": {
title: "File Access Denied",
userMessage: "Cannot access the selected file.",
severity: "medium",
recoverable: true,
retryable: false,
suggestions: [
"Make sure the file is not open in another application",
"Check file permissions",
"Try copying the file to a different location first"
]
},
// Generic errors
"unknown-error": {
title: "Unknown Error",
userMessage: "An unexpected error occurred.",
severity: "medium",
recoverable: true,
retryable: true,
suggestions: [
"Try the operation again",
"Refresh the page if the problem persists",
"Contact support if you continue to experience issues"
]
}
};
const processError = (error, context = {}, config) => {
const timestamp = /* @__PURE__ */ new Date();
const errorId = `error_${timestamp.getTime()}_${Math.random().toString(36).substring(2, 11)}`;
let code;
let technicalMessage;
let type = "unknown";
if (typeof error === "string") {
code = "unknown-error";
technicalMessage = error;
} else if (error && typeof error === "object" && "code" in error && "type" in error) {
code = error.code;
technicalMessage = error.message || "Validation error occurred";
type = error.type === "size" ? "validation" : error.type === "type" ? "validation" : error.type === "count" ? "validation" : error.type === "dimensions" ? "validation" : error.type === "network" ? "network" : "validation";
} else if (error && typeof error === "object" && "code" in error) {
code = error.code;
technicalMessage = error.message || "Error occurred";
} else if (error instanceof Error) {
code = error.name.toLowerCase().replace(/error$/, "") || "unknown-error";
technicalMessage = error.message;
if (error.message.includes("network") || error.message.includes("fetch")) {
type = "network";
} else if (error.message.includes("timeout")) {
type = "timeout";
} else if (error.message.includes("permission") || error.message.includes("denied")) {
type = "permission";
} else if (error.message.includes("quota") || error.message.includes("storage")) {
type = "quota";
}
} else if (error === null || error === void 0) {
code = "unknown-error";
technicalMessage = "Unknown error occurred";
} else {
code = "unknown-error";
technicalMessage = "Unknown error occurred";
}
const errorDetails = ERROR_MESSAGES[code] || ERROR_MESSAGES["unknown-error"];
let userMessage = errorDetails.userMessage;
if (context.fileName) {
userMessage = `${userMessage} (File: ${context.fileName})`;
}
const suggestions = [...errorDetails.suggestions];
if (config && context.fileName) {
if (code === "file-too-large" && config.validation.maxSize) {
const maxSizeMB = (config.validation.maxSize / 1024 / 1024).toFixed(1);
suggestions.unshift(`Maximum file size is ${maxSizeMB} MB`);
}
if (code === "invalid-file-type" && config.validation.allowedTypes.length > 0) {
suggestions.unshift(`Supported types: ${config.validation.allowedTypes.join(", ")}`);
}
}
const actions = [];
if (errorDetails.retryable) {
actions.push({
id: "retry",
label: config?.labels.retryText || "Try Again",
type: "retry",
primary: true
});
}
if (errorDetails.recoverable) {
actions.push({
id: "remove",
label: config?.labels.removeText || "Remove File",
type: "remove"
});
}
actions.push({
id: "clear",
label: "Clear All",
type: "clear"
});
if (errorDetails.severity === "high" || errorDetails.severity === "critical") {
actions.push({
id: "contact",
label: "Contact Support",
type: "contact"
});
}
return {
id: errorId,
type,
code,
title: errorDetails.title,
message: technicalMessage,
userMessage,
technicalMessage,
severity: errorDetails.severity,
recoverable: errorDetails.recoverable,
retryable: errorDetails.retryable,
context: {
...context,
timestamp,
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
},
suggestions,
actions
};
};
const processErrors = (errors, context = {}, config) => {
const processedErrors = errors.map((error) => processError(error, context, config));
const summary = {
total: processedErrors.length,
byType: {},
bySeverity: {},
retryable: 0,
recoverable: 0
};
processedErrors.forEach((error) => {
summary.byType[error.type] = (summary.byType[error.type] || 0) + 1;
summary.bySeverity[error.severity] = (summary.bySeverity[error.severity] || 0) + 1;
if (error.retryable) summary.retryable++;
if (error.recoverable) summary.recoverable++;
});
return {
errors: processedErrors,
summary
};
};
const formatErrorForUser = (error, includeContext = true) => {
let message = error.userMessage;
if (includeContext && error.context.fileName) {
message += ` (${error.context.fileName})`;
}
if (error.suggestions.length > 0) {
message += `
Suggestions:
${error.suggestions.map((s) => `• ${s}`).join("\n")}`;
}
return message;
};
const shouldAnnounceError = (error) => {
return error.severity === "high" || error.severity === "critical" || !error.recoverable;
};
const createErrorAnnouncement = (error) => {
const severity = error.severity === "critical" ? "Critical error: " : error.severity === "high" ? "Error: " : "";
return `${severity}${error.title}. ${error.userMessage}`;
};
const logError = (error, additionalContext) => {
const logData = {
errorId: error.id,
type: error.type,
code: error.code,
severity: error.severity,
message: error.technicalMessage,
context: error.context,
...additionalContext
};
if (error.severity === "critical") {
console.error("Critical FileUpload Error:", logData);
} else if (error.severity === "high") {
console.error("FileUpload Error:", logData);
} else {
console.warn("FileUpload Warning:", logData);
}
};
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;
};
exports.applyTheme = theme.applyTheme;
exports.cn = theme.cn;
exports.createThemeWatcher = theme.createThemeWatcher;
exports.generateCSSVariables = theme.generateCSSVariables;
exports.generateThemeClasses = theme.generateThemeClasses;
exports.getResponsiveClasses = theme.getResponsiveClasses;
exports.getSystemTheme = theme.getSystemTheme;
exports.resolveTheme = theme.resolveTheme;
exports.validateThemeConfig = theme.validateThemeConfig;
exports.createErrorAnnouncement = createErrorAnnouncement;
exports.createValidationError = createValidationError;
exports.formatErrorForUser = formatErrorForUser;
exports.formatFileSize = formatFileSize;
exports.getFileExtension = getFileExtension;
exports.isAudioFile = isAudioFile;
exports.isDocumentFile = isDocumentFile;
exports.isImageFile = isImageFile;
exports.isVideoFile = isVideoFile;
exports.logError = logError;
exports.processError = processError;
exports.processErrors = processErrors;
exports.shouldAnnounceError = shouldAnnounceError;
exports.validateAcceptAttribute = validateAcceptAttribute;
exports.validateFile = validateFile;
exports.validateFileCount = validateFileCount;
exports.validateFileSize = validateFileSize;
exports.validateFileType = validateFileType;
exports.validateFiles = validateFiles;
exports.validateImageDimensions = validateImageDimensions;
//# sourceMappingURL=utils.cjs.map