1/** 2 * Zephyr File Upload Component 3 * 4 * Alpine.js component for file upload with drag-and-drop support 5 */ 6document.addEventListener('alpine:init', function() { 7 Alpine.data('zephyrFileUpload', function(config) { 8 return { 9 // Config from PHP 10 hasExistingFile: config.hasExistingFile || false, 11 actionValue: config.actionValue || 'save_new', 12 allowedExtensions: (config.allowedExtensions || '').split(',').filter(function(e) { return e; }), 13 maxFileSize: config.maxFileSize || 10485760, 14 15 // State 16 hasFile: config.hasExistingFile || false, 17 showUploader: !config.hasExistingFile, 18 isDragging: false, 19 fileName: '', 20 fileSize: '', 21 previewUrl: null, 22 isImage: false, 23 errorMessage: '', 24 validationFailed: false, 25 26 init: function() { 27 // Component initialized 28 }, 29 30 toggleUploader: function() { 31 this.showUploader = true; 32 this.actionValue = 'save_new'; 33 this.errorMessage = ''; 34 this.hasFile = false; 35 }, 36 37 cancelChange: function() { 38 this.showUploader = false;
39 this.clearFile(); 40 this.actionValue = 'save_old'; 41 }, 42 43 handleDrop: function(e) { 44 this.isDragging = false; 45 var files = e.dataTransfer.files; 46 if (files.length) { 47 if (this.validateFile(files[0])) { 48 this.$refs.fileInput.files = files; 49 this.processFile(files[0]); 50 } 51 } 52 }, 53 54 handleFileSelect: function(e) { 55 var file = e.target.files[0]; 56 if (file) { 57 if (this.validateFile(file)) { 58 this.processFile(file); 59 } else { 60 e.target.value = ''; 61 } 62 } 63 }, 64 65 validateFile: function(file) { 66 var self = this; 67 this.errorMessage = ''; 68 69 // Check file size 70 if (file.size > this.maxFileSize) { 71 var maxSizeMB = (this.maxFileSize / (1024 * 1024)).toFixed(0); 72 var fileSizeMB = (file.size / (1024 * 1024)).toFixed(2); 73 this.errorMessage = 'File is too large (' + fileSizeMB + 'MB). Maximum size is ' + maxSizeMB + 'MB.'; 74 return false; 75 } 76 77 // Check file extension if restrictions are specified 78 if (this.allowedExtensions.length > 0) { 79 var fileName = file.name; 80 var ext = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); 81 82 // Clean up extensions (remove dots and spaces) 83 var cleanExtensions = this.allowedExtensions.map(function(e) { 84 return e.replace('.', '').trim().toLowerCase(); 85 }); 86 87 if (cleanExtensions.indexOf(ext) === -1) { 88 this.errorMessage = 'Invalid file type (.' + ext.toUpperCase() + '). Allowed types: ' + cleanExtensions.join(', ').toUpperCase(); 89 return false; 90 } 91 } 92 93 return true; 94 }, 95 96 processFile: function(file) { 97 var self = this; 98 this.fileName = file.name; 99 this.fileSize = this.formatFileSize(file.size); 100 this.isImage = file.type.indexOf('image/') === 0; 101 this.actionValue = 'save_new'; 102 this.errorMessage = ''; 103 this.hasFile = true; 104 this.validationFailed = false; 105 106 if (this.isImage) { 107 var reader = new FileReader(); 108 reader.onload = function(e) { 109 self.previewUrl = e.target.result; 110 }; 111 reader.readAsDataURL(file); 112 } else { 113 this.previewUrl = 'file'; 114 } 115 116 // Trigger Magento price update 117 if (typeof opConfig !== 'undefined') { 118 opConfig.reloadPrice(); 119 } 120 }, 121 122 clearFile: function() { 123 this.fileName = ''; 124 this.fileSize = ''; 125 this.previewUrl = null; 126 this.isImage = false; 127 this.errorMessage = ''; 128 this.$refs.fileInput.value = ''; 129 130 if (this.hasExistingFile) { 131 this.actionValue = 'save_old'; 132 this.hasFile = true; 133 } else { 134 this.actionValue = 'save_new'; 135 this.hasFile = false; 136 } 137 }, 138 139 formatFileSize: function(bytes) { 140 if (bytes === 0) return '0 Bytes'; 141 var k = 1024; 142 var sizes = ['Bytes', 'KB', 'MB', 'GB']; 143 var i = Math.floor(Math.log(bytes) / Math.log(k)); 144 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; 145 } 146 }; 147 }); 148});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.