diff --git a/assets/js/fm.js b/assets/js/fm.js index 8fb2c06..c867d91 100644 --- a/assets/js/fm.js +++ b/assets/js/fm.js @@ -1,5 +1,10 @@ document.addEventListener('DOMContentLoaded', () => { - if (!document.getElementById('upload-form')) return; + const form = document.getElementById('upload-form'); + if (!form) return; + const upload = document.getElementById('file-upload'); + const uploadLink = document.getElementById('file-upload-link'); + + const uploadField = document.getElementById('field-upload'); const neverExpireCheckbox = document.getElementById('field-never_expire'); const expireAfterDaysField = document.getElementById('field-expire_after_days'); @@ -14,4 +19,114 @@ document.addEventListener('DOMContentLoaded', () => { autogenUrlCheckbox.addEventListener('change', () => { slugField.disabled = autogenUrlCheckbox.checked; }); -}); \ No newline at end of file + + let uploadForm; + + form.addEventListener('submit', e => { + e.preventDefault(); + if (!uploadForm || uploadForm.isFinished()) { + uploadForm = new UploadForm(form, upload, uploadLink, uploadField.files[0].name); + uploadForm.updateView(); + uploadForm.start(); + } + }); +}); + +function UploadForm(form, upload, uploadLink, fileName) { + this.form = form; + this.upload = upload; + this.uploadLink = uploadLink; + this.fileName = fileName; + this.progressBar = this.upload.querySelector('.progress-bar'); + this.progressBarContent = this.progressBar.querySelector('.content'); + this.status = this.upload.querySelector('.status'); + this.speed = this.status.querySelector('.speed'); + this.finished = false; + + this.xferSpeed = []; + this.lastTransferTime = null; + + this.xhr = new XMLHttpRequest(); + this.xhr.responseType = 'json'; + this.xhr.upload.addEventListener('progress', e => { + if (e.lengthComputable) { + this.progressBar.classList.remove('undefined'); + let percent = ((e.loaded / e.total) * 100).toFixed(2) + '%'; + this.progressBar.style = `--progress: ${percent}`; + this.progressBarContent.innerText = percent; + this.updateSpeed(e.loaded); + } else { + this.progressBar.classList.add('undefined'); + } + }); + this.xhr.upload.addEventListener('loadstart', () => { + this.status.classList.remove('hidden'); + }); + this.xhr.addEventListener('load', () => { + this.finished = true; + let response = this.xhr.response; + console.log('done', response); + if (response.status === 'error') { + if (response.messages) { + this.restoreView(); + window.applyFormMessages(this.form, response.messages); + } + } else if (response.url) { + this.status.innerHTML = 'Done!'; + this.uploadLink.querySelector('.content').innerText = response.url; + this.uploadLink.classList.remove('hidden'); + } else { + window.location.reload(); + } + }); + this.xhr.addEventListener('error', (e) => { + this.finished = true; + console.error('error', e); + this.status.innerHTML = 'Error; upload was interrupted.'; + }); +} + +UploadForm.prototype.isFinished = function () { + return this.finished; +} + +UploadForm.prototype.updateView = function () { + this.upload.querySelector('.name').innerText = this.fileName; + this.form.classList.add('hidden'); + this.upload.classList.remove('hidden'); + this.status.innerHTML = `Uploading @ --`; + this.speed = this.status.querySelector('.speed'); +} + +UploadForm.prototype.restoreView = function () { + this.status.classList.add('hidden'); + this.upload.classList.add('hidden'); + this.form.classList.remove('hidden'); +}; + +UploadForm.prototype.start = function () { + const formData = new FormData(this.form); + this.xhr.open('POST', this.form.action); + this.xhr.send(formData); +} + +const units = ['K', 'M', 'G', 'T']; +UploadForm.prototype.updateSpeed = function (loaded) { + console.log(this.xferSpeed); + const time = new Date().getTime(); + if (this.lastTransferTime) { + this.xferSpeed.push((loaded - this.lastLoaded) / (time - this.lastTransferTime)); + + if (this.xferSpeed.length > 10) this.xferSpeed = this.xferSpeed.slice(1); + + let speed = this.xferSpeed.reduce((v, c) => v + c) / this.xferSpeed.length; + let unit = 0; + while (speed >= 1000 && unit < units.length - 1) { + speed /= 1000; + unit++; + } + this.speed.innerText = (speed).toFixed(2) + units[unit] + 'Bps'; + } + this.lastTransferTime = time; + this.lastLoaded = loaded; +} diff --git a/assets/sass/app.scss b/assets/sass/app.scss index 933fea1..0a709c7 100644 --- a/assets/sass/app.scss +++ b/assets/sass/app.scss @@ -1 +1,2 @@ -@import "layout"; \ No newline at end of file +@import "layout"; +@import "fm"; \ No newline at end of file diff --git a/assets/sass/fm.scss b/assets/sass/fm.scss new file mode 100644 index 0000000..6f5df35 --- /dev/null +++ b/assets/sass/fm.scss @@ -0,0 +1,11 @@ +@import "vars"; + +#file-upload { + padding: 8px; + background: $infoColor; + border-radius: 5px; + + .name, .status { + text-align: center; + } +} \ No newline at end of file diff --git a/views/file-upload.njk b/views/file-upload.njk index 0d6a36b..ad798ce 100644 --- a/views/file-upload.njk +++ b/views/file-upload.njk @@ -14,7 +14,8 @@

Upload a file

-
+ {{ macros.field(_locals, 'file', 'upload', '', 'Choose wisely', 'The maximum upload size is ' + max_upload_size + 'MiB', validation_attributes='required') }} {{ macros.field(_locals, 'number', 'expire_after_days', '30', 'How many days to delete this file after', null, validation_attributes='max="1825"') }} @@ -27,6 +28,18 @@
+ + + +
diff --git a/yarn.lock b/yarn.lock index 0587f97..0f4dd94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9925,9 +9925,9 @@ widest-line@^3.1.0: string-width "^4.0.0" wms-core@^0: - version "0.8.10" - resolved "https://registry.toot.party/wms-core/-/wms-core-0.8.10.tgz#8db64926f9bd9eaa61556ad98dcd215361b16950" - integrity sha512-1GJkpOX/efoDGK9JOwHnBkD55Q6dEKTQsdHpgMmF3g0h7XxJFAAsZTv0tOyfYHIEpJo5mNdR/+mzdZTPsgtAdw== + version "0.8.11" + resolved "https://registry.toot.party/wms-core/-/wms-core-0.8.11.tgz#7b33600e3dff864f71e5a8bd57e185cf324b0332" + integrity sha512-We+PYb+D80gVj/VWUUYDazZCf/BiEjybi32Z51BEM73ihcmFuCOnD9FUq4Oy3MK/Vl6wVctWdapIb2jVnrSPug== dependencies: "@types/express" "^4.17.6" "@types/express-session" "^1.17.0"