UNPKG

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
"use strict"; 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