1jQuery.widget('gc.tusUploader', { 2 loadingFiles: [], 3 options: { 4 owner: null, 5 onSuccess: null, 6 progressContainer: null, 7 accept: null, 8 }, 9 _create: function (callback) { 10 var self = this; 11 12 if (!tus.isSupported) { 13 console.log('Tus uploader not supported for this browser'); 14 return; 15 } 16 17 const $uploaderContainer = this.element; 18 if (this.options.progressContainer === null) { 19 this.options.progressContainer = $uploaderContainer; 20 } 21 22 const $btn = $('<label class="tus-uploader-btn">' + Yii.t('common', 'Upload') + '</label>').appendTo($uploaderContainer); 23 const $input = $('<input type="file" multiple style="display: none;" />').appendTo($btn); 24 if (this.options.accept) { 25 $input.attr('accept', this.options.accept); 26 } 27 28 $input[0].addEventListener("change", function (e) { 29 /** @var {FileList} files */ 30 const files = e.target.files; 31 for (let i = 0; i < files.length; i++) { 32 const file = files[i]; 33 const fileKey = self.composeFileFingerprint(file); 34 if (self.loadingFiles.includes(fileKey)) { 35 console.log('File already in upload queue'); 36 37 continue; 38 } 39 self.loadingFiles.push(fileKey); 40 41 self.uploadFile.call(self, file); 42 } 43 }); 44 }, 45 /** @param {File} file */ 46 uploadFile: function (file) { 47 var self = this; 48 49 let $title = $('<div class="clearfix tus-uploader-title" />').html(file.name) 50 .appendTo(self.options.progressContainer); 51 52 const progress = this.createProgressBar(self.options.progressContainer); 53 54 const target = this.createUploadTarget(file); 55 56 let uploadIsActive = false; 57 58 const $resumeBtn = $('<button class="btn btn-default btn-xs">ÐÑодолжиÑÑ</button>') 59 .click(function () { 60 if (!uploadIsActive) { 61 self.resume(upload); 62 onResume(); 63 } 64 }) 65 .hide().appendTo($title); 66 67 const $processBtn = $('<span class="glyphicon glyphicon-play tus-progress-icon" />') 68 .click(function () { 69 if (uploadIsActive) { 70 upload.abort(); 71 onPause(); 72 } else { 73 self.resume(upload); 74 onResume(); 75 } 76 }).hide().appendTo(progress.progressBar); 77 78 const $alert = $('<span class="text-danger" />').hide().appendTo($title); 79 80 const onPause = function () { 81 uploadIsActive = false; 82 $resumeBtn.show(); 83 $processBtn 84 .addClass('glyphicon-play') 85 .removeClass('glyphicon-pause') 86 .attr('title', 'ÐÑодолжиÑÑ').show(); 87 progress.pause(); 88 }; 89 const onResume = function () { 90 uploadIsActive = true; 91 $resumeBtn.hide(); 92 $processBtn 93 .addClass('glyphicon-pause') 94 .removeClass('glyphicon-play') 95 .attr('title', 'ÐÑÑановиÑÑ').show(); 96 $alert.hide().html(''); 97 progress.active(); 98 }; 99 100 var hasOnlineListener = false;
101 102 var chunkUploadStart = new Date(); 103 const chunkSizeDefault = 10000000; 104 const chunkSizeMultiplier = 2; 105 const chunkSizeMax = 1000000000; 106 const chunkUploadSuccessTime = 15; 107 108 // Create a new tus upload 109 const upload = new tus.Upload(file, { 110 endpoint: target, 111 chunkSize: chunkSizeDefault, 112 retryDelays: [200, 1000, 10000, 60000], 113 metadata: { 114 filename: file.name, 115 filetype: file.type 116 }, 117 removeFingerprintOnSuccess: true, 118 fingerprint: function fingerprint(file, options, callback) { 119 // removed options.endpoint from original 120 return callback( 121 null, 122 self.composeFileFingerprint(file) 123 ); 124 }, 125 onError: function (error) { 126 this.chunkSize = chunkSizeDefault; 127 if (navigator.onLine === false) { 128 $alert.html('ÐÐµÑ Ð¿Ð¾Ð´ÐºÐ»ÑÑÐµÐ½Ð¸Ñ Ðº ÑеÑи').show(); 129 130 if (hasOnlineListener === false) { 131 hasOnlineListener = true; 132 window.addEventListener('online', function (e) { 133 hasOnlineListener = false;
134 self.resume(upload); 135 onResume(); 136 }, { 137 once: true, 138 }); 139 } 140 } else { 141 $alert.html('ÐÑибка загÑÑзки').show(); 142 } 143 144 if (error.originalRequest) { 145 onPause(); 146 } 147 148 progress.failed(); 149 }, 150 onProgress: function (bytesUploaded, bytesTotal) { 151 let percentage = (bytesUploaded / bytesTotal * 100).toFixed(2); 152 progress.setPercent(percentage); 153 }, 154 onChunkComplete: function (chunkSize, bytesAccepted, bytesTotal) { 155 let now = new Date(); 156 let chunkUploadDuration = (now - chunkUploadStart) / 1000; 157 let uploadSpeed = Math.round(chunkSize / chunkUploadDuration / (1024 * 1024) * 100) / 100; 158 console.debug("Uploaded chunk " + chunkSize + " (" + bytesAccepted + " / " + bytesTotal + "), duration: " + chunkUploadDuration + ", speed: " + uploadSpeed); 159 chunkUploadStart = now; 160 // increase chunk size for fast upload 161 if (chunkUploadDuration < chunkUploadSuccessTime && this.chunkSize < chunkSizeMax) { 162 this.chunkSize = Math.round(this.chunkSize * chunkSizeMultiplier); 163 console.debug("Increase chunk size " + chunkSize + " -> " + this.chunkSize); 164 } 165 // decrease chunk size for slow upload 166 if (chunkUploadDuration > chunkUploadSuccessTime * 2 && this.chunkSize > chunkSizeDefault) { 167 this.chunkSize = Math.round(this.chunkSize / chunkSizeMultiplier); 168 console.debug("Decrease chunk size " + chunkSize + " -> " + this.chunkSize); 169 } 170 }, 171 onSuccess: function () { 172 $processBtn.hide(); 173 progress.finish(); 174 if (self.options.onSuccess) { 175 self.options.onSuccess(upload.url, upload.file); 176 } 177 }, 178 }); 179 180 // Start the upload 181 upload.start(); 182 onResume(); 183 }, 184 resume: function (upload) { 185 try { 186 // update secure link 187 if (upload.url) { 188 const uploadUrl = new URL(upload.url); 189 if (uploadUrl.searchParams.get('e') - ((new Date()).getTime() / 1000) < 3600) { 190 const newUrl = this.createUploadTarget(upload.file, uploadUrl.href, uploadUrl.host); 191 if (newUrl) { 192 upload.url = newUrl; 193 } 194 } 195 } 196 } catch (e) { 197 console.log(e); 198 } 199 200 upload.start(); 201 }, 202 /** @param {File} file */ 203 composeFileFingerprint: function (file) { 204 return ["tus-br", file.name, file.type, file.size, file.lastModified].join("-"); 205 }, 206 /** 207 * @param {File} fileInfo 208 * @param {String} [uri] 209 * @param {String} [host] 210 */ 211 createUploadTarget: function (fileInfo, uri, host) { 212 let target; 213 var tusUri = '/fileservice/tus'; 214 215 try { 216 var session = localStorage.getItem('session'); 217 var requestParams = {"fs_ref": window.location.href}; 218 219 if (session !== undefined && session != null){ 220 var objSession = jQuery.parseJSON(session); 221 222 if (objSession.hasOwnProperty('user_id') && objSession.user_id != null) { 223 requestParams.fs_u = objSession.user_id; 224 } else { 225 requestParams.fs_u = -1; 226 } 227 } 228 229 tusUri += '?' + jQuery.param(requestParams); 230 } catch (err) { 231 } 232 233 $.ajax({ 234 url: '/fileservice/widget/create-secret-link', 235 method: 'GET', 236 data: { 237 host: host ? host : window.fileserviceUploadHost, 238 uri: uri ? uri : tusUri, 239 expires: 12 * 3600, 240 fileInfo: { 241 name: fileInfo.name, 242 size: fileInfo.size, 243 type: fileInfo.type, 244 }, 245 }, 246 success: function (data) { 247 if (data.link) { 248 target = data.link; 249 } 250 }, 251 error: function (http, message, exc) {sendCreateLinkError(http, message, exc);}, 252 async: false 253 }); 254 255 return target; 256 }, 257 createProgressBar: function (uploaderContainer) { 258 const progress = { 259 progressBarContainer: null, 260 progressBar: null, 261 progressText: null, 262 init: function (container) {
263 this.progressBarContainer = $('<div class="progress" />').appendTo(container); 264 this.progressBar = $('<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" />').appendTo(this.progressBarContainer); 265 this.progressText = $('<span />').appendTo(this.progressBar); 266 }, 267 setPercent: function (value) { 268 this.progressBar.attr({ 269 'aria-valuenow': value, 270 }); 271 this.progressBar.css({ 272 'width': value + '%', 273 }); 274 this.progressText.html(value + '%'); 275 }, 276 active: function () { 277 this.progressBar 278 .addClass("progress-bar-success progress-bar-striped active") 279 .removeClass("progress-bar-warning progress-bar-danger"); 280 }, 281 pause: function () { 282 this.progressBar 283 .addClass("progress-bar-warning") 284 .removeClass("progress-bar-danger progress-bar-success progress-bar-striped active"); 285 }, 286 failed: function () { 287 this.progressBar 288 .addClass("progress-bar-danger") 289 .removeClass("progress-bar-warning progress-bar-success progress-bar-striped active"); 290 }, 291 finish: function () { 292 this.progressBar 293 .removeClass("progress-bar-striped active"); 294 }, 295 }; 296 progress.init(uploaderContainer); 297 298 return progress; 299 }, 300});
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.